Skip to content
Snippets Groups Projects
cscan 12.1 KiB
Newer Older
James K. Lowden's avatar
James K. Lowden committed
#! /usr/bin/env python3

# Copyright (c) 2020, Symas Corporation
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
# 
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#
# Use pycparser to report casts in a C file.
#
from __future__ import print_function
import sys, re
from os import environ, getcwd
from getopt import getopt
from subprocess import CalledProcessError

# In case not installed in site-packages/ with setup.py
sys.path.extend(['.', '..'])

from pycparser import c_parser, c_ast, parse_file
from pycparser.plyparser import ParseError

verbose = False
    
def string_of(node):
    if isinstance(node, c_ast.ArrayDecl):
        return '%s %s[%s]' % (string_of(node.type),
                              'name', 
                              ' '.join(string_of(node.dim_quals),
                                       string_of(node.dim)))
    if isinstance(node, c_ast.ArrayRef):
        return '%s[%s]' % (string_of(node.name),
                           string_of(node.subscript))
    if isinstance(node, c_ast.Assignment):
        return '%s %s %s' % (string_of(node.lvalue),
                             string_of(node.op),
                             string_of(node.rvalue))
    if isinstance(node, c_ast.BinaryOp):
        return '%s %s %s' % (string_of(node.left), 
                             string_of(node.op), 
                             string_of(node.right))
    if isinstance(node, c_ast.Break):
        return 'break?'
    if isinstance(node, c_ast.Case):
        return '%s %s' % (string_of(node.expr),
                          string_of(node.stmts))
    if isinstance(node, c_ast.Cast):
        return '(%s) (%s)' % (string_of(node.to_type),
                              string_of(node.expr))
    if isinstance(node, c_ast.Compound):
        return '%s' % (string_of(node.block_items))
    if isinstance(node, c_ast.CompoundLiteral):
        return '%s %s' % ( string_of(node.init),
                           string_of(node.type) )
    if isinstance(node, c_ast.Constant):
        return '%s %s' % (string_of(node.type),
                          string_of(node.value))
    # class Continue(Node)
    if isinstance(node, c_ast.Decl):
        return '%s/%s/%s/%s/%s/%s/%s' % ( string_of(node.bitsize), 
                                          string_of(node.funcspec), 
                                          string_of(node.init), 
                                          string_of(node.name), 
                                          string_of(node.quals), 
                                          string_of(node.storage), 
                                          string_of(node.type) )
    if isinstance(node, c_ast.DeclList):
        return '%s' % string_of(node.decls)
    if isinstance(node, c_ast.Default):
        return '%s' % string_of(node.stmts)
    if isinstance(node, c_ast.DoWhile):
        return 'do/while (%s) { %s }' % (string_of(node.cond), 
                                         string_of(node.stmt))
    #class EllipsisParam(Node)
    #class EmptyStatement(Node)
    if isinstance(node, c_ast.Enum):
        return '%s {%s}' % ( string_of(node.name), 
                             string_of(node.values) ) 
    if isinstance(node, c_ast.Enumerator):
        return '%s %s' % ( string_of(node.name),
                       string_of(node.value) )
    if isinstance(node, c_ast.EnumeratorList):
        return '%s %s' % ( string_of(node.enumerators) ) 
    if isinstance(node, c_ast.ExprList):
        return '%s' % (  string_of(node.exprs) )
    # FileAST
    if isinstance(node, c_ast.For):
        return 'for(%s; %s; %s {%s}' % ( string_of(node.init), 
                                         string_of(node.cond), 
                                         string_of(node.next), 
                                         string_of(node.stmt) )
    if isinstance(node, c_ast.FuncCall):
        return '%s(%s)' % ( string_of(node.name), 
                            string_of(node.args) )
    if isinstance(node, c_ast.FuncDecl):
        return '%s %s(%s)' % ( string_of(node.type),
                               'funcname',
                               string_of(node.args) )
    if isinstance(node, c_ast.FuncDef):
        return '%s %s() {%s}' % ( string_of(node.decl), 
                              string_of(node.param_decls), 
                              string_of(node.body) )
    if isinstance(node, c_ast.Goto):
        return 'goto %s' % ( string_of(node.name) )
    if isinstance(node, c_ast.ID):
        assert(isinstance(node.name, str))
        return '%s' % ( node.name )
    if isinstance(node, c_ast.IdentifierType):
        for name in node.names:
            assert(isinstance(name, str))
        return '%s' % ( ' '.join(node.names) )
    if isinstance(node, c_ast.If):
        return 'if (%s) {%s} else {%s}' % ( string_of(node.cond), 
                                            string_of(node.iftrue), 
                                            string_of(node.iffalse) ) 
    if isinstance(node, c_ast.InitList):
        return '%s %s' % ( string_of(node.exprs) ) 
    if isinstance(node, c_ast.Label):
        return '%s: %s' % ( string_of(node.name), 
                            string_of(node.stmt) )
    if isinstance(node, c_ast.NamedInitializer):
        return '%s %s' % ( string_of(node.name), 
                           string_of(node.expr) )
    # Node
    # NodeVisitor
    if isinstance(node, c_ast.ParamList):
        return '%s' % ( string_of(node.params) )
    if isinstance(node, c_ast.Pragma):
        return '#pragma %s' % ( string_of(node.string) )
    if isinstance(node, c_ast.PtrDecl):
        return '%s*' % ' '.join( ( string_of(node.quals), 
                                  string_of(node.type) ) )
    if isinstance(node, c_ast.Return):
        return 'return %s' % ( string_of(node.expr) )
    if isinstance(node, c_ast.Struct):
        return '%s {%s}' % ( string_of(node.name), 
                             string_of(node.decls) ) 
    if isinstance(node, c_ast.StructRef):
        return '%s%s%s' % ( string_of(node.name), 
                            string_of(node.type), 
                            string_of(node.field) )
    if isinstance(node, c_ast.Switch):
        return 'switch %s (%s)' % ( string_of(node.cond), 
                                    string_of(node.stmt) ) 
    if isinstance(node, c_ast.TernaryOp):
        return '%s ? %s : %s' % ( string_of(node.cond), 
                                  string_of(node.iftrue), 
                                  string_of(node.iffalse) )
    if isinstance(node, c_ast.TypeDecl):
        return '%s %s' % ( ' '.join( (string_of(node.quals), 
                                      string_of(node.declname)) ),
                           string_of(node.type) )
    if isinstance(node, c_ast.Typedef):
        return '%s %s' % ( ' '.join( (string_of(node.quals), 
                                      string_of(node.storage), 
                                      string_of(node.type)) ),
                           string_of(node.name) )
    if isinstance(node, c_ast.Typename):
        output = '%s %s' % ( ' '.join( (string_of(node.quals), 
                                        string_of(node.type))),
                             string_of(node.name) )
        # typename.qual and PtrDecl.TypeDecl.quals may both say 'const'
        output = re.sub(r'(const +)+', 'const ', output)
        return output.strip()
    if isinstance(node, c_ast.UnaryOp):
        return '%s%s' % ( string_of(node.op), 
                          string_of(node.expr) ) 
    if isinstance(node, c_ast.Union):
        return 'union %s {%s}' % ( string_of(node.name), 
                                   string_of(node.decls) )
    if isinstance(node, c_ast.While):
        return 'while (%s) (%s)' % ( string_of(node.cond), 
                                     string_of(node.stmt) )

    if isinstance(node, tuple) or isinstance(node, list):
        elems = [ string_of(x) for x in node ]
        return ' '.join(elems)

    if isinstance(node, str):
        return node

    if node is None:
        return ''

    print( 'error: string_of: unhandled type "%s"' % str(type(node)) )

def cast_pair(node):
    assert( isinstance(node, c_ast.Cast) )
    def class_name(node):
        return re.split(r'\W+', str(type(node)))[-2]
    def coord(node):
        return '%s:%s' % ( node.coord.file, node.coord.line )

    if verbose:
        print( 'cast: %s of %s' % (class_name(node.to_type),
                                   class_name(node.expr)) )
        #node.show(offset=2, showcoord=True)
        #print( '----' )
    return '%s: %-10s %s' % (coord(node),
                           class_name(node.expr) + ':',
                           string_of(node)) 

def read_ignores( filename ):
    ignoring = []
    with open(filename) as f:
        for input in f:
            elements = input.split(':') 
            if len(elements) > 2:
                output = ':'.join( elements[:2] )
                ignoring.append(output)
                #rint(output)
    return ignoring
        
James K. Lowden's avatar
James K. Lowden committed
# Visitor for Cast nodes, to report where a cast is used. 
class CastVisitor(c_ast.NodeVisitor):
    ncast = 0
    ignoring = ()
    def __init__(self, ignoring):
        self.ignoring = ignoring
James K. Lowden's avatar
James K. Lowden committed
    def visit_Cast(self, node):
        key = '%s:%d' % (node.coord.file, node.coord.line)
        if not key in self.ignoring:
            print( cast_pair(node) )
            self.ncast += 1
        else:
            pass # print('%s: (ignored)' % key)
            
def show_casts(cpp, filename, ignoring):
James K. Lowden's avatar
James K. Lowden committed
    cpp_args = re.split(r'\s{2,}', environ['CSCAN_FLAGS'])
    assert( len(cpp_args) > 1 )
    ast = parse_file(filename, use_cpp=True,
                     cpp_path=cpp, 
                     cpp_args=cpp_args)

    try:
        ignoring += read_ignores('.cscan-ignore')
        print( 'ignoring %d approved casts' % len(ignoring) )
    except Exception as oops:
        print(oops);
    
    v = CastVisitor(ignoring)
James K. Lowden's avatar
James K. Lowden committed
    v.visit(ast)
James K. Lowden's avatar
James K. Lowden committed

if __name__ == "__main__":
    cpp = 'cpp'
James K. Lowden's avatar
James K. Lowden committed
    
    try:
        opts, args = getopt(sys.argv[1:], "E:W:v")
James K. Lowden's avatar
James K. Lowden committed
    except getopt.GetoptError as err:
        print(err)
        exit(1)

    narg = 1
    for narg, (o, a) in enumerate(opts, start=2):
        if o == "-E":
            cpp = a
        elif o == '-v':
            verbose = True
        elif o == '-W':
            ignoring += read_ignores(a)
James K. Lowden's avatar
James K. Lowden committed
        else:
            assert False, "unhandled option"

    exit_status = 0
    
    for filename in sys.argv[narg:]:
        print( 'scanning "%s"' % filename )
        try: 
            exit_status = show_casts(cpp, filename, ignoring)
            if exit_status > 0:
                break
James K. Lowden's avatar
James K. Lowden committed
        except CalledProcessError as erc:
            print( '%s in %s' % (erc, getcwd()), file=sys.stderr )
            exit(1)
        except ParseError as erc:
            print( 'cscan error at\n%s in %s' % (erc, getcwd()),
                   file=sys.stderr )
            if verbose:
                raise RuntimeError("verbose causes traceback") from erc
            exit_status = 1
        except BrokenPipeError as erc:
            exit(1)

    exit(exit_status)