Creating a BGE staging branch.
[blender.git] / build_files / cmake / clang_array_check.py
1 # ---
2 # * Licensed under the Apache License, Version 2.0 (the "License");
3 # * you may not use this file except in compliance with the License.
4 # * You may obtain a copy of the License at
5 # *
6 # * http://www.apache.org/licenses/LICENSE-2.0
7 # ---
8 # by Campbell Barton
9
10 """
11 Invocation:
12
13    export CLANG_BIND_DIR="/dsk/src/llvm/tools/clang/bindings/python"
14    export CLANG_LIB_DIR="/opt/llvm/lib"
15
16    python2 clang_array_check.py somefile.c -DSOME_DEFINE -I/some/include
17
18 ... defines and includes are optional
19
20 """
21
22 # delay parsing functions until we need them
23 USE_LAZY_INIT = True
24 USE_EXACT_COMPARE = False
25
26 # -----------------------------------------------------------------------------
27 # predefined function/arg sizes, handy sometimes, but not complete...
28
29 defs_precalc = {
30     "glColor3bv": {0: 3},
31     "glColor4bv": {0: 4},
32
33     "glColor3ubv": {0: 3},
34     "glColor4ubv": {0: 4},
35
36     "glColor4usv": {0: 3},
37     "glColor4usv": {0: 4},
38
39     "glColor3fv": {0: 3},
40     "glColor4fv": {0: 4},
41
42     "glColor3dv": {0: 3},
43     "glColor4dv": {0: 4},
44     
45     "glVertex2fv": {0: 2},
46     "glVertex3fv": {0: 3},
47     "glVertex4fv": {0: 4},
48
49     "glEvalCoord1fv": {0: 1},
50     "glEvalCoord1dv": {0: 1},
51     "glEvalCoord2fv": {0: 2},
52     "glEvalCoord2dv": {0: 2},
53     
54     "glRasterPos2dv": {0: 2},
55     "glRasterPos3dv": {0: 3},
56     "glRasterPos4dv": {0: 4},
57     
58     "glRasterPos2fv": {0: 2},
59     "glRasterPos3fv": {0: 3},
60     "glRasterPos4fv": {0: 4},
61     
62     "glRasterPos2sv": {0: 2},
63     "glRasterPos3sv": {0: 3},
64     "glRasterPos4sv": {0: 4},
65     
66     "glTexCoord2fv": {0: 2},
67     "glTexCoord3fv": {0: 3},
68     "glTexCoord4fv": {0: 4},
69     
70     "glTexCoord2dv": {0: 2},
71     "glTexCoord3dv": {0: 3},
72     "glTexCoord4dv": {0: 4},
73     
74     "glNormal3fv": {0: 3},
75     "glNormal3dv": {0: 3},
76     "glNormal3bv": {0: 3},
77     "glNormal3iv": {0: 3},
78     "glNormal3sv": {0: 3},
79 }
80
81 # -----------------------------------------------------------------------------
82
83 import sys
84
85 if 0:
86     # Examples with LLVM as the root dir: '/dsk/src/llvm'
87     
88     # path containing 'clang/__init__.py'
89     CLANG_BIND_DIR = "/dsk/src/llvm/tools/clang/bindings/python"
90     
91     # path containing libclang.so
92     CLANG_LIB_DIR = "/opt/llvm/lib"
93 else:
94     import os
95     CLANG_BIND_DIR = os.environ.get("CLANG_BIND_DIR")
96     CLANG_LIB_DIR = os.environ.get("CLANG_LIB_DIR")
97     
98     if CLANG_BIND_DIR is None:
99         print("$CLANG_BIND_DIR python binding dir not set")
100     if CLANG_LIB_DIR is None:
101         print("$CLANG_LIB_DIR clang lib dir not set")
102
103 sys.path.append(CLANG_BIND_DIR)
104
105 import clang
106 import clang.cindex
107 from clang.cindex import (CursorKind,
108                           TypeKind,
109                           TokenKind)
110
111 clang.cindex.Config.set_library_path(CLANG_LIB_DIR)
112
113 index = clang.cindex.Index.create()
114
115 args = sys.argv[2:]
116 # print(args)
117
118 tu = index.parse(sys.argv[1], args)
119 # print('Translation unit: %s' % tu.spelling)
120
121 # -----------------------------------------------------------------------------
122
123
124 def function_parm_wash_tokens(parm):
125     # print(parm.kind)
126     assert parm.kind in (CursorKind.PARM_DECL,
127                          CursorKind.VAR_DECL,  # XXX, double check this
128                          CursorKind.FIELD_DECL,
129                          )
130     
131     """
132     Return tolens without trailing commads and 'const'
133     """
134
135     tokens = [t for t in parm.get_tokens()]
136     if not tokens:
137         return tokens
138     
139     #if tokens[-1].kind == To
140     # remove trailing char
141     if tokens[-1].kind == TokenKind.PUNCTUATION:
142         if tokens[-1].spelling in (",", ")", ";"):
143             tokens.pop()
144         #else:
145         #    print(tokens[-1].spelling)
146
147     t_new = []
148     for t in tokens:
149         t_kind = t.kind
150         t_spelling = t.spelling
151         ok = True
152         if t_kind == TokenKind.KEYWORD:
153             if t_spelling in ("const", "restrict", "volatile"):
154                 ok = False
155             elif t_spelling.startswith("__"):
156                 ok = False  # __restrict
157         elif t_kind in (TokenKind.COMMENT, ):
158             ok = False
159             
160             # Use these
161         elif t_kind in (TokenKind.LITERAL,
162                         TokenKind.PUNCTUATION,
163                         TokenKind.IDENTIFIER):
164             # use but ignore
165             pass
166         
167         else:
168             print("Unknown!", t_kind, t_spelling)
169         
170         # if its OK we will add
171         if ok:
172             t_new.append(t)
173     return t_new
174
175
176 def parm_size(node_child):
177     tokens = function_parm_wash_tokens(node_child)
178     
179     # print(" ".join([t.spelling for t in tokens]))
180     
181     # NOT PERFECT CODE, EXTRACT SIZE FROM TOKENS
182     if len(tokens) >= 3:  # foo [ 1 ]
183         if      ((tokens[-3].kind == TokenKind.PUNCTUATION and tokens[-3].spelling == "[") and
184                  (tokens[-2].kind == TokenKind.LITERAL and tokens[-2].spelling.isdigit()) and
185                  (tokens[-1].kind == TokenKind.PUNCTUATION and tokens[-1].spelling == "]")):
186             # ---
187             return int(tokens[-2].spelling)
188     return -1
189
190
191 def function_get_arg_sizes(node):
192     # Return a dict if (index: size) items
193     # {arg_indx: arg_array_size, ... ]
194     arg_sizes = {}
195
196     if 1:  # node.spelling == "BM_vert_create", for debugging
197         node_parms = [node_child for node_child in node.get_children()
198                       if node_child.kind == CursorKind.PARM_DECL]
199
200         for i, node_child in enumerate(node_parms):
201
202             # print(node_child.kind, node_child.spelling)
203             #print(node_child.type.kind, node_child.spelling)  # TypeKind.POINTER
204             
205             if node_child.type.kind == TypeKind.POINTER:
206                 pointee = node_child.type.get_pointee()
207                 if pointee.is_pod():
208                     size = parm_size(node_child)
209                     if size != -1:
210                         arg_sizes[i] = size
211
212     return arg_sizes
213
214
215 # -----------------------------------------------------------------------------
216 _defs = {}
217
218
219 def lookup_function_size_def(func_id):
220     if USE_LAZY_INIT:
221         result = _defs.get(func_id, {})
222         if type(result) != dict:
223             result = _defs[func_id] = function_get_arg_sizes(result)
224         return result
225     else:
226         return _defs.get(func_id, {})
227
228 # -----------------------------------------------------------------------------
229
230
231 def file_check_arg_sizes(tu):
232     
233     # main checking function
234     def validate_arg_size(node):
235         """
236         Loop over args and validate sizes for args we KNOW the size of.
237         """
238         assert node.kind == CursorKind.CALL_EXPR
239         
240         if 0:
241             print("---",
242                   " <~> ".join(
243                   [" ".join([t.spelling for t in C.get_tokens()])
244                   for C in node.get_children()]
245                   ))
246         # print(node.location)
247         
248         # first child is the function call, skip that.
249         children = list(node.get_children())
250
251         if not children:
252             return  # XXX, look into this, happens on C++
253         
254         func = children[0]
255         
256         # get the func declaration!
257         # works but we can better scan for functions ahead of time.
258         if 0:
259             func_dec = func.get_definition()
260             if func_dec:
261                 print("FD", " ".join([t.spelling for t in func_dec.get_tokens()]))
262             else:
263                 # HRMP'f - why does this fail?
264                 print("AA", " ".join([t.spelling for t in node.get_tokens()]))
265         else:
266             args_size_definition = ()  # dummy
267             
268             # get the key
269             tok = list(func.get_tokens())
270             if tok:
271                 func_id = tok[0].spelling
272                 args_size_definition = lookup_function_size_def(func_id)
273         
274         if not args_size_definition:
275             return
276
277         children = children[1:]
278         for i, node_child in enumerate(children):
279             children = list(node_child.get_children())
280             
281             # skip if we dont have an index...
282             size_def = args_size_definition.get(i, -1)
283
284             if size_def == -1:
285                 continue
286             
287             #print([c.kind for c in children])
288             # print(" ".join([t.spelling for t in node_child.get_tokens()]))
289             
290             if len(children) == 1:
291                 arg = children[0]
292                 if arg.kind in (CursorKind.DECL_REF_EXPR,
293                                 CursorKind.UNEXPOSED_EXPR):
294
295                     if arg.type.kind == TypeKind.POINTER:
296                         dec = arg.get_definition()
297                         if dec:
298                             size = parm_size(dec)
299                             
300                             # size == 0 is for 'float *a'
301                             if size != -1 and size != 0:
302                                 
303                                 # nice print!
304                                 if 0:
305                                     print("".join([t.spelling for t in func.get_tokens()]),
306                                           i,
307                                           " ".join([t.spelling for t in dec.get_tokens()]))
308
309                                 # testing
310                                 # size_def = 100
311                                 if size != 1:
312                                     if USE_EXACT_COMPARE:
313                                         # is_err = (size != size_def) and (size != 4 and size_def != 3)
314                                         is_err = (size != size_def)
315                                     else:
316                                         is_err = (size < size_def)
317
318                                     if is_err:
319                                         location = node.location
320                                         # if "math_color_inline.c" not in str(location.file):
321                                         if 1:
322                                             print("%s:%d:%d: argument %d is size %d, should be %d (from %s)" %
323                                                   (location.file,
324                                                    location.line,
325                                                    location.column,
326                                                    i + 1, size, size_def,
327                                                    args[0]  # always the same but useful when running threaded
328                                                    ))
329
330     # we dont really care what we are looking at, just scan entire file for
331     # function calls.
332
333     def recursive_func_call_check(node):
334         if node.kind == CursorKind.CALL_EXPR:
335             validate_arg_size(node)
336
337         for c in node.get_children():
338             recursive_func_call_check(c)
339
340     recursive_func_call_check(tu.cursor)
341
342
343 # -- first pass, cache function definitions sizes
344
345 # PRINT FUNC DEFINES
346 def recursive_arg_sizes(node, ):
347     # print(node.kind, node.spelling)
348     if node.kind == CursorKind.FUNCTION_DECL:
349         if USE_LAZY_INIT:
350             args_sizes = node
351         else:
352             args_sizes = function_get_arg_sizes(node)
353         #if args_sizes:
354         #    print(node.spelling, args_sizes)
355         _defs[node.spelling] = args_sizes
356         # print("adding", node.spelling)
357     for c in node.get_children():
358         recursive_arg_sizes(c)
359 # cache function sizes
360 recursive_arg_sizes(tu.cursor)
361 _defs.update(defs_precalc)
362
363 # --- second pass, check against def's
364 file_check_arg_sizes(tu)