e955c94ed3
Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
389 lines
11 KiB
Python
389 lines
11 KiB
Python
# SPDX-FileCopyrightText: 2012 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""
|
|
Invocation:
|
|
|
|
export CLANG_BIND_DIR="/dsk/src/llvm/tools/clang/bindings/python"
|
|
export CLANG_LIB_DIR="/opt/llvm/lib"
|
|
|
|
python 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},
|
|
|
|
"glColor3usv": {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},
|
|
|
|
# GPU immediate mode.
|
|
"immVertex2iv": {1: 2},
|
|
|
|
"immVertex2fv": {1: 2},
|
|
"immVertex3fv": {1: 3},
|
|
|
|
"immAttr2fv": {1: 2},
|
|
"immAttr3fv": {1: 3},
|
|
"immAttr4fv": {1: 4},
|
|
|
|
"immAttr3ubv": {1: 3},
|
|
"immAttr4ubv": {1: 4},
|
|
|
|
"immUniform2fv": {1: 2},
|
|
"immUniform3fv": {1: 3},
|
|
"immUniform4fv": {1: 4},
|
|
|
|
"immUniformColor3fv": {0: 3},
|
|
"immUniformColor4fv": {0: 4},
|
|
|
|
"immUniformColor3ubv": {1: 3},
|
|
"immUniformColor4ubv": {1: 4},
|
|
|
|
"immUniformColor3fvAlpha": {0: 3},
|
|
"immUniformColor4fvAlpha": {0: 4},
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
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")
|
|
|
|
if CLANG_BIND_DIR:
|
|
sys.path.append(CLANG_BIND_DIR)
|
|
|
|
import clang
|
|
import clang.cindex
|
|
from clang.cindex import (CursorKind,
|
|
TypeKind,
|
|
TokenKind)
|
|
|
|
if CLANG_LIB_DIR:
|
|
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 tokens 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 don't 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 don't 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)
|