Merge branch 'blender2.7'
[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 filepath = tu.spelling
121
122 # -----------------------------------------------------------------------------
123
124
125 def function_parm_wash_tokens(parm):
126     # print(parm.kind)
127     assert parm.kind in (CursorKind.PARM_DECL,
128                          CursorKind.VAR_DECL,  # XXX, double check this
129                          CursorKind.FIELD_DECL,
130                          )
131
132     """
133     Return tolens without trailing commands and 'const'
134     """
135
136     tokens = [t for t in parm.get_tokens()]
137     if not tokens:
138         return tokens
139
140     # if tokens[-1].kind == To
141     # remove trailing char
142     if tokens[-1].kind == TokenKind.PUNCTUATION:
143         if tokens[-1].spelling in (",", ")", ";"):
144             tokens.pop()
145         # else:
146         #     print(tokens[-1].spelling)
147
148     t_new = []
149     for t in tokens:
150         t_kind = t.kind
151         t_spelling = t.spelling
152         ok = True
153         if t_kind == TokenKind.KEYWORD:
154             if t_spelling in ("const", "restrict", "volatile"):
155                 ok = False
156             elif t_spelling.startswith("__"):
157                 ok = False  # __restrict
158         elif t_kind in (TokenKind.COMMENT, ):
159             ok = False
160
161             # Use these
162         elif t_kind in (TokenKind.LITERAL,
163                         TokenKind.PUNCTUATION,
164                         TokenKind.IDENTIFIER):
165             # use but ignore
166             pass
167
168         else:
169             print("Unknown!", t_kind, t_spelling)
170
171         # if its OK we will add
172         if ok:
173             t_new.append(t)
174     return t_new
175
176
177 def parm_size(node_child):
178     tokens = function_parm_wash_tokens(node_child)
179
180     # print(" ".join([t.spelling for t in tokens]))
181
182     # NOT PERFECT CODE, EXTRACT SIZE FROM TOKENS
183     if len(tokens) >= 3:  # foo [ 1 ]
184         if      ((tokens[-3].kind == TokenKind.PUNCTUATION and tokens[-3].spelling == "[") and
185                  (tokens[-2].kind == TokenKind.LITERAL and tokens[-2].spelling.isdigit()) and
186                  (tokens[-1].kind == TokenKind.PUNCTUATION and tokens[-1].spelling == "]")):
187             # ---
188             return int(tokens[-2].spelling)
189     return -1
190
191
192 def function_get_arg_sizes(node):
193     # Return a dict if (index: size) items
194     # {arg_indx: arg_array_size, ... ]
195     arg_sizes = {}
196
197     if 1:  # node.spelling == "BM_vert_create", for debugging
198         node_parms = [node_child for node_child in node.get_children()
199                       if node_child.kind == CursorKind.PARM_DECL]
200
201         for i, node_child in enumerate(node_parms):
202
203             # print(node_child.kind, node_child.spelling)
204             # print(node_child.type.kind, node_child.spelling)
205             if node_child.type.kind == TypeKind.CONSTANTARRAY:
206                 pointee = node_child.type.get_pointee()
207                 size = parm_size(node_child)
208                 if size != -1:
209                     arg_sizes[i] = size
210
211     return arg_sizes
212
213
214 # -----------------------------------------------------------------------------
215 _defs = {}
216
217
218 def lookup_function_size_def(func_id):
219     if USE_LAZY_INIT:
220         result = _defs.get(func_id, {})
221         if type(result) != dict:
222             result = _defs[func_id] = function_get_arg_sizes(result)
223         return result
224     else:
225         return _defs.get(func_id, {})
226
227 # -----------------------------------------------------------------------------
228
229
230 def file_check_arg_sizes(tu):
231
232     # main checking function
233     def validate_arg_size(node):
234         """
235         Loop over args and validate sizes for args we KNOW the size of.
236         """
237         assert node.kind == CursorKind.CALL_EXPR
238
239         if 0:
240             print("---",
241                   " <~> ".join(
242                       [" ".join([t.spelling for t in C.get_tokens()])
243                        for C in node.get_children()]
244                   ))
245         # print(node.location)
246
247         # first child is the function call, skip that.
248         children = list(node.get_children())
249
250         if not children:
251             return  # XXX, look into this, happens on C++
252
253         func = children[0]
254
255         # get the func declaration!
256         # works but we can better scan for functions ahead of time.
257         if 0:
258             func_dec = func.get_definition()
259             if func_dec:
260                 print("FD", " ".join([t.spelling for t in func_dec.get_tokens()]))
261             else:
262                 # HRMP'f - why does this fail?
263                 print("AA", " ".join([t.spelling for t in node.get_tokens()]))
264         else:
265             args_size_definition = ()  # dummy
266
267             # get the key
268             tok = list(func.get_tokens())
269             if tok:
270                 func_id = tok[0].spelling
271                 args_size_definition = lookup_function_size_def(func_id)
272
273         if not args_size_definition:
274             return
275
276         children = children[1:]
277         for i, node_child in enumerate(children):
278             children = list(node_child.get_children())
279
280             # skip if we dont have an index...
281             size_def = args_size_definition.get(i, -1)
282
283             if size_def == -1:
284                 continue
285
286             # print([c.kind for c in children])
287             # print(" ".join([t.spelling for t in node_child.get_tokens()]))
288
289             if len(children) == 1:
290                 arg = children[0]
291                 if arg.kind in (CursorKind.DECL_REF_EXPR,
292                                 CursorKind.UNEXPOSED_EXPR):
293
294                     if arg.type.kind == TypeKind.CONSTANTARRAY:
295                         dec = arg.get_definition()
296                         if dec:
297                             size = parm_size(dec)
298
299                             # size == 0 is for 'float *a'
300                             if size != -1 and size != 0:
301
302                                 # nice print!
303                                 if 0:
304                                     print("".join([t.spelling for t in func.get_tokens()]),
305                                           i,
306                                           " ".join([t.spelling for t in dec.get_tokens()]))
307
308                                 # testing
309                                 # size_def = 100
310                                 if size != 1:
311                                     if USE_EXACT_COMPARE:
312                                         # is_err = (size != size_def) and (size != 4 and size_def != 3)
313                                         is_err = (size != size_def)
314                                     else:
315                                         is_err = (size < size_def)
316
317                                     if is_err:
318                                         location = node.location
319                                         # if "math_color_inline.c" not in str(location.file):
320                                         if 1:
321                                             print("%s:%d:%d: argument %d is size %d, should be %d (from %s)" %
322                                                   (location.file,
323                                                    location.line,
324                                                    location.column,
325                                                    i + 1, size, size_def,
326                                                    filepath  # always the same but useful when running threaded
327                                                    ))
328
329     # we dont really care what we are looking at, just scan entire file for
330     # function calls.
331
332     def recursive_func_call_check(node):
333         if node.kind == CursorKind.CALL_EXPR:
334             validate_arg_size(node)
335
336         for c in node.get_children():
337             recursive_func_call_check(c)
338
339     recursive_func_call_check(tu.cursor)
340
341
342 # -- first pass, cache function definitions sizes
343
344 # PRINT FUNC DEFINES
345 def recursive_arg_sizes(node, ):
346     # print(node.kind, node.spelling)
347     if node.kind == CursorKind.FUNCTION_DECL:
348         if USE_LAZY_INIT:
349             args_sizes = node
350         else:
351             args_sizes = function_get_arg_sizes(node)
352         # if args_sizes:
353         #     print(node.spelling, args_sizes)
354         _defs[node.spelling] = args_sizes
355         # print("adding", node.spelling)
356     for c in node.get_children():
357         recursive_arg_sizes(c)
358 # cache function sizes
359 recursive_arg_sizes(tu.cursor)
360 _defs.update(defs_precalc)
361
362 # --- second pass, check against def's
363 file_check_arg_sizes(tu)