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