123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- # ---
- # * Licensed under the Apache License, Version 2.0 (the "License");
- # * you may not use this file except in compliance with the License.
- # * You may obtain a copy of the License at
- # *
- # * http://www.apache.org/licenses/LICENSE-2.0
- # ---
- # by Campbell Barton
- """
- Invocation:
- export CLANG_BIND_DIR="/dsk/src/llvm/tools/clang/bindings/python"
- export CLANG_LIB_DIR="/opt/llvm/lib"
- python2 clang_array_check.py somefile.c -DSOME_DEFINE -I/some/include
- ... defines and includes are optional
- """
- # delay parsing functions until we need them
- USE_LAZY_INIT = True
- USE_EXACT_COMPARE = False
- # -----------------------------------------------------------------------------
- # predefined function/arg sizes, handy sometimes, but not complete...
- defs_precalc = {
- "glColor3bv": {0: 3},
- "glColor4bv": {0: 4},
- "glColor3ubv": {0: 3},
- "glColor4ubv": {0: 4},
- "glColor4usv": {0: 3},
- "glColor4usv": {0: 4},
- "glColor3fv": {0: 3},
- "glColor4fv": {0: 4},
- "glColor3dv": {0: 3},
- "glColor4dv": {0: 4},
- "glVertex2fv": {0: 2},
- "glVertex3fv": {0: 3},
- "glVertex4fv": {0: 4},
- "glEvalCoord1fv": {0: 1},
- "glEvalCoord1dv": {0: 1},
- "glEvalCoord2fv": {0: 2},
- "glEvalCoord2dv": {0: 2},
- "glRasterPos2dv": {0: 2},
- "glRasterPos3dv": {0: 3},
- "glRasterPos4dv": {0: 4},
- "glRasterPos2fv": {0: 2},
- "glRasterPos3fv": {0: 3},
- "glRasterPos4fv": {0: 4},
- "glRasterPos2sv": {0: 2},
- "glRasterPos3sv": {0: 3},
- "glRasterPos4sv": {0: 4},
- "glTexCoord2fv": {0: 2},
- "glTexCoord3fv": {0: 3},
- "glTexCoord4fv": {0: 4},
- "glTexCoord2dv": {0: 2},
- "glTexCoord3dv": {0: 3},
- "glTexCoord4dv": {0: 4},
- "glNormal3fv": {0: 3},
- "glNormal3dv": {0: 3},
- "glNormal3bv": {0: 3},
- "glNormal3iv": {0: 3},
- "glNormal3sv": {0: 3},
- }
- # -----------------------------------------------------------------------------
- import sys
- if 0:
- # Examples with LLVM as the root dir: '/dsk/src/llvm'
- # path containing 'clang/__init__.py'
- CLANG_BIND_DIR = "/dsk/src/llvm/tools/clang/bindings/python"
- # path containing libclang.so
- CLANG_LIB_DIR = "/opt/llvm/lib"
- else:
- import os
- CLANG_BIND_DIR = os.environ.get("CLANG_BIND_DIR")
- CLANG_LIB_DIR = os.environ.get("CLANG_LIB_DIR")
- if CLANG_BIND_DIR is None:
- print("$CLANG_BIND_DIR python binding dir not set")
- if CLANG_LIB_DIR is None:
- print("$CLANG_LIB_DIR clang lib dir not set")
- sys.path.append(CLANG_BIND_DIR)
- import clang
- import clang.cindex
- from clang.cindex import (CursorKind,
- TypeKind,
- TokenKind)
- clang.cindex.Config.set_library_path(CLANG_LIB_DIR)
- index = clang.cindex.Index.create()
- args = sys.argv[2:]
- # print(args)
- tu = index.parse(sys.argv[1], args)
- # print('Translation unit: %s' % tu.spelling)
- filepath = tu.spelling
- # -----------------------------------------------------------------------------
- def function_parm_wash_tokens(parm):
- # print(parm.kind)
- assert parm.kind in (CursorKind.PARM_DECL,
- CursorKind.VAR_DECL, # XXX, double check this
- CursorKind.FIELD_DECL,
- )
- """
- Return tolens without trailing commands and 'const'
- """
- tokens = [t for t in parm.get_tokens()]
- if not tokens:
- return tokens
- # if tokens[-1].kind == To
- # remove trailing char
- if tokens[-1].kind == TokenKind.PUNCTUATION:
- if tokens[-1].spelling in (",", ")", ";"):
- tokens.pop()
- # else:
- # print(tokens[-1].spelling)
- t_new = []
- for t in tokens:
- t_kind = t.kind
- t_spelling = t.spelling
- ok = True
- if t_kind == TokenKind.KEYWORD:
- if t_spelling in ("const", "restrict", "volatile"):
- ok = False
- elif t_spelling.startswith("__"):
- ok = False # __restrict
- elif t_kind in (TokenKind.COMMENT, ):
- ok = False
- # Use these
- elif t_kind in (TokenKind.LITERAL,
- TokenKind.PUNCTUATION,
- TokenKind.IDENTIFIER):
- # use but ignore
- pass
- else:
- print("Unknown!", t_kind, t_spelling)
- # if its OK we will add
- if ok:
- t_new.append(t)
- return t_new
- def parm_size(node_child):
- tokens = function_parm_wash_tokens(node_child)
- # print(" ".join([t.spelling for t in tokens]))
- # NOT PERFECT CODE, EXTRACT SIZE FROM TOKENS
- if len(tokens) >= 3: # foo [ 1 ]
- if ((tokens[-3].kind == TokenKind.PUNCTUATION and tokens[-3].spelling == "[") and
- (tokens[-2].kind == TokenKind.LITERAL and tokens[-2].spelling.isdigit()) and
- (tokens[-1].kind == TokenKind.PUNCTUATION and tokens[-1].spelling == "]")):
- # ---
- return int(tokens[-2].spelling)
- return -1
- def function_get_arg_sizes(node):
- # Return a dict if (index: size) items
- # {arg_indx: arg_array_size, ... ]
- arg_sizes = {}
- if 1: # node.spelling == "BM_vert_create", for debugging
- node_parms = [node_child for node_child in node.get_children()
- if node_child.kind == CursorKind.PARM_DECL]
- for i, node_child in enumerate(node_parms):
- # print(node_child.kind, node_child.spelling)
- # print(node_child.type.kind, node_child.spelling)
- if node_child.type.kind == TypeKind.CONSTANTARRAY:
- pointee = node_child.type.get_pointee()
- size = parm_size(node_child)
- if size != -1:
- arg_sizes[i] = size
- return arg_sizes
- # -----------------------------------------------------------------------------
- _defs = {}
- def lookup_function_size_def(func_id):
- if USE_LAZY_INIT:
- result = _defs.get(func_id, {})
- if type(result) != dict:
- result = _defs[func_id] = function_get_arg_sizes(result)
- return result
- else:
- return _defs.get(func_id, {})
- # -----------------------------------------------------------------------------
- def file_check_arg_sizes(tu):
- # main checking function
- def validate_arg_size(node):
- """
- Loop over args and validate sizes for args we KNOW the size of.
- """
- assert node.kind == CursorKind.CALL_EXPR
- if 0:
- print("---",
- " <~> ".join(
- [" ".join([t.spelling for t in C.get_tokens()])
- for C in node.get_children()]
- ))
- # print(node.location)
- # first child is the function call, skip that.
- children = list(node.get_children())
- if not children:
- return # XXX, look into this, happens on C++
- func = children[0]
- # get the func declaration!
- # works but we can better scan for functions ahead of time.
- if 0:
- func_dec = func.get_definition()
- if func_dec:
- print("FD", " ".join([t.spelling for t in func_dec.get_tokens()]))
- else:
- # HRMP'f - why does this fail?
- print("AA", " ".join([t.spelling for t in node.get_tokens()]))
- else:
- args_size_definition = () # dummy
- # get the key
- tok = list(func.get_tokens())
- if tok:
- func_id = tok[0].spelling
- args_size_definition = lookup_function_size_def(func_id)
- if not args_size_definition:
- return
- children = children[1:]
- for i, node_child in enumerate(children):
- children = list(node_child.get_children())
- # skip if we dont have an index...
- size_def = args_size_definition.get(i, -1)
- if size_def == -1:
- continue
- # print([c.kind for c in children])
- # print(" ".join([t.spelling for t in node_child.get_tokens()]))
- if len(children) == 1:
- arg = children[0]
- if arg.kind in (CursorKind.DECL_REF_EXPR,
- CursorKind.UNEXPOSED_EXPR):
- if arg.type.kind == TypeKind.CONSTANTARRAY:
- dec = arg.get_definition()
- if dec:
- size = parm_size(dec)
- # size == 0 is for 'float *a'
- if size != -1 and size != 0:
- # nice print!
- if 0:
- print("".join([t.spelling for t in func.get_tokens()]),
- i,
- " ".join([t.spelling for t in dec.get_tokens()]))
- # testing
- # size_def = 100
- if size != 1:
- if USE_EXACT_COMPARE:
- # is_err = (size != size_def) and (size != 4 and size_def != 3)
- is_err = (size != size_def)
- else:
- is_err = (size < size_def)
- if is_err:
- location = node.location
- # if "math_color_inline.c" not in str(location.file):
- if 1:
- print("%s:%d:%d: argument %d is size %d, should be %d (from %s)" %
- (location.file,
- location.line,
- location.column,
- i + 1, size, size_def,
- filepath # always the same but useful when running threaded
- ))
- # we dont really care what we are looking at, just scan entire file for
- # function calls.
- def recursive_func_call_check(node):
- if node.kind == CursorKind.CALL_EXPR:
- validate_arg_size(node)
- for c in node.get_children():
- recursive_func_call_check(c)
- recursive_func_call_check(tu.cursor)
- # -- first pass, cache function definitions sizes
- # PRINT FUNC DEFINES
- def recursive_arg_sizes(node, ):
- # print(node.kind, node.spelling)
- if node.kind == CursorKind.FUNCTION_DECL:
- if USE_LAZY_INIT:
- args_sizes = node
- else:
- args_sizes = function_get_arg_sizes(node)
- # if args_sizes:
- # print(node.spelling, args_sizes)
- _defs[node.spelling] = args_sizes
- # print("adding", node.spelling)
- for c in node.get_children():
- recursive_arg_sizes(c)
- # cache function sizes
- recursive_arg_sizes(tu.cursor)
- _defs.update(defs_precalc)
- # --- second pass, check against def's
- file_check_arg_sizes(tu)
|