#      Copyright (c) 2023, an unpublished work by CodeSecure, Inc.
#                      ALL RIGHTS RESERVED
#
#      Copyright (c) 2017-2023, an unpublished work by GrammaTech, Inc.
#                      ALL RIGHTS RESERVED
#
#      This software is furnished under a license and may be used and
#      copied only in accordance with the terms of such license and the       
#      inclusion of the above copyright notice.  This software or any
#      other copies thereof may not be provided or otherwise made
#      available to any other person.  Title to and ownership of the
#      software is retained by CodeSecure, Inc.
#

# UnsafeCast_plugin.py
# 
# A small example plug-in that reports casts from const to non-const
# types.

# All Python plug-ins for CodeSonar must import the cs module.
import cs


# badcast is a new \link WarningClasses/WarningClasses.html warning
# class \endlink defined by this plug-in. It must be 
# created in the top level scope.
#
# We do not associate any \link
# WarningClasses/MnemonicHierarchy.html CodeSonar mnemonics \endlink
# or \link WarningClasses/CWE/CWE.html CWE identifiers 
# \endlink with the new class, as none are appropriate. 

badcast = cs.analysis.create_warningclass(
    'Cast from const to non-const',
    '',
    1,
    cs.warningclass_flags.WARNING_POINT_IS_START_POINT,
    cs.warning_significance.SECURITY )


# Set up the pattern in global scope.
try:
    pat = cs.ast_pattern('''
       (c:cast
          :2 (?e
            :type (c:pointer
              :pointed-to (?c :is-const #t)))
          :type (c:pointer
            :pointed-to (?k :is-const #f)))''')
except cs.ast_pattern_compilation_error as e:
    print(str(e))
    print('   - pattern: ', e.get_pattern())                   # cs.ast_pattern_compilation_error.get_pattern()


# Visitors should be at the finest granularity that is appropriate
# for the check: in this case, we are checking the AST associated
# with a point.

@cs.point_visitor                                             # \link API/PythonAPI/visitor.html visitor decorator \endlink
def check_cast(pt):
    # Retrieve the procedure that contains pt.    
    proc = pt.get_procedure()                                 # cs.point.get_procedure()

    # Check if the procedure was authored by the user.  If not, don't
    # produce warnings about this code, since the user probably isn't
    # interested about warnings internal to library models or
    # synthesized procedures.
    if proc.get_kind() != cs.procedure_kind.USER_DEFINED:     # cs.procedure.get_kind()
        return

    # Get the \link C_Module/API/AST/csaf_c_normalized.html
    # normalized C/C++ AST\endlink associated with pt. If there isn't
    # one (for whatever reason), there is no point in proceeding.
    if pt.has_ast(cs.ast_family.C_NORMALIZED):                # cs.point.has_ast()
        ptast = pt.get_ast(cs.ast_family.C_NORMALIZED)        # cs.point.get_ast()
    else:
        print('point has no normalized C/C++ AST')
        return
       
    # An offending cast might be nested inside other expressions, so
    # we need to check every subtree of ptast against our pattern.
    if next((subast
             for subast in ptast.traverse()                   # cs.ast.traverse()
             if pat.match(subast)),                           # cs.ast_pattern.match()
            False):
        badcast.report(pt, 'unsafe cast')                     # cs.warningclass.report()
        return
