comparison OrthancFramework/Resources/CheckOrthancFrameworkSymbols.py @ 4303:44b53a2c0a13

improving detection of ABI compatibility
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 06 Nov 2020 15:37:30 +0100
parents b30a8de92ad9
children 50b0c69b653a
comparison
equal deleted inserted replaced
4302:4c91fbede7d2 4303:44b53a2c0a13
77 f.write('#include "%s"\n' % os.path.join(ROOT, '..', 'Sources', 'OrthancFramework.h')) 77 f.write('#include "%s"\n' % os.path.join(ROOT, '..', 'Sources', 'OrthancFramework.h'))
78 for source in SOURCES: 78 for source in SOURCES:
79 f.write('#include "%s"\n' % source) 79 f.write('#include "%s"\n' % source)
80 80
81 81
82 tu = index.parse(AMALGAMATION, 82 tu = index.parse(AMALGAMATION, [
83 [ '-DORTHANC_BUILDING_FRAMEWORK_LIBRARY=1' ]) 83 '-DORTHANC_BUILDING_FRAMEWORK_LIBRARY=1',
84 '-DORTHANC_BUILD_UNIT_TESTS=0',
85 '-DORTHANC_SANDBOXED=0',
86 '-DORTHANC_ENABLE_BASE64=1',
87 '-DORTHANC_ENABLE_CIVETWEB=1',
88 '-DORTHANC_ENABLE_CURL=1',
89 '-DORTHANC_ENABLE_DCMTK=1',
90 '-DORTHANC_ENABLE_DCMTK_JPEG=1',
91 '-DORTHANC_ENABLE_DCMTK_NETWORKING=1',
92 '-DORTHANC_ENABLE_DCMTK_TRANSCODING=1',
93 '-DORTHANC_ENABLE_JPEG=1',
94 '-DORTHANC_ENABLE_LOCALE=1',
95 '-DORTHANC_ENABLE_LUA=1',
96 '-DORTHANC_ENABLE_LOGGING=1',
97 '-DORTHANC_ENABLE_MD5=1',
98 '-DORTHANC_ENABLE_PKCS11=1',
99 '-DORTHANC_ENABLE_PNG=1',
100 '-DORTHANC_ENABLE_PUGIXML=1',
101 '-DORTHANC_ENABLE_SSL=1',
102 '-DORTHANC_SQLITE_STANDALONE=0',
103 '-DORTHANC_ENABLE_LOGGING_STDIO=0',
104 ])
84 105
85 106
86 FILES = [] 107 FILES = []
87 COUNT = 0 108 COUNT = 0
88 109
110 def ReportProblem(message, fqn, cursor):
111 global FILES, COUNT
112 FILES.append(os.path.normpath(str(cursor.location.file)))
113 COUNT += 1
114
115 print('%s: %s::%s()' % (message, '::'.join(fqn), cursor.spelling))
116
117
89 def ExploreClass(child, fqn): 118 def ExploreClass(child, fqn):
119 # Safety check
120 if (child.kind != clang.cindex.CursorKind.CLASS_DECL and
121 child.kind != clang.cindex.CursorKind.STRUCT_DECL):
122 raise Exception()
123
124 # Ignore forward declaration of classes
125 if not child.is_definition():
126 return
127
128
129 ##
130 ## Verify that the class is publicly exported (its visibility must
131 ## be "default")
132 ##
90 visible = False 133 visible = False
91 134
92 for i in child.get_children(): 135 for i in child.get_children():
93 if (i.kind == clang.cindex.CursorKind.VISIBILITY_ATTR and 136 if (i.kind == clang.cindex.CursorKind.VISIBILITY_ATTR and
94 i.spelling == 'default'): 137 i.spelling == 'default'):
95 visible = True 138 visible = True
96 139
97 if visible: 140 if not visible:
98 isPublic = (child.kind == clang.cindex.CursorKind.STRUCT_DECL) 141 return
142
143
144 ##
145 ## Ignore pure abstract interfaces, by checking the following
146 ## criteria:
147 ## - It must be a C++ class (not a struct)
148 ## - The class name must start with "I"
149 ## - All its methods must be pure virtual (abstract) and public
150 ## - Its destructor must be public, virtual, and must do nothing
151 ##
152
153 if (child.kind == clang.cindex.CursorKind.CLASS_DECL and
154 fqn[-1].startswith('I')):
155 abstract = True
156 isPublic = False
99 157
100 for i in child.get_children(): 158 for i in child.get_children():
101 if i.kind == clang.cindex.CursorKind.CXX_ACCESS_SPEC_DECL: 159 if i.kind == clang.cindex.CursorKind.VISIBILITY_ATTR: # "default"
160 pass
161 elif i.kind == clang.cindex.CursorKind.CXX_ACCESS_SPEC_DECL:
102 isPublic = (i.access_specifier == clang.cindex.AccessSpecifier.PUBLIC) 162 isPublic = (i.access_specifier == clang.cindex.AccessSpecifier.PUBLIC)
103 163 elif i.kind == clang.cindex.CursorKind.CXX_BASE_SPECIFIER:
104 elif (i.kind == clang.cindex.CursorKind.CLASS_DECL or 164 if i.spelling != 'boost::noncopyable':
105 i.kind == clang.cindex.CursorKind.STRUCT_DECL): 165 abstract = False
106 # This is a subclass 166 elif isPublic:
167 if i.kind == clang.cindex.CursorKind.CXX_METHOD:
168 if i.is_pure_virtual_method():
169 pass # pure virtual is ok
170 elif i.is_static_method():
171 # static method without an inline implementation is ok
172 for j in i.get_children():
173 if j.kind == clang.cindex.CursorKind.COMPOUND_STMT:
174 abstract = False
175 else:
176 abstract = False
177 elif (i.kind == clang.cindex.CursorKind.DESTRUCTOR and
178 i.is_virtual_method()):
179 # The destructor must be virtual, and must do nothing
180 c = list(i.get_children())
181 if (len(c) != 1 or
182 c[0].kind != clang.cindex.CursorKind.COMPOUND_STMT or
183 len(list(c[0].get_children())) != 0):
184 abstract = False
185 elif (i.kind == clang.cindex.CursorKind.CLASS_DECL or
186 i.kind == clang.cindex.CursorKind.STRUCT_DECL):
187 ExploreClass(i, fqn + [ i.spelling ])
188 else:
189 abstract = False
190
191 if abstract:
192 print('Detected a pure interface (this is fine): %s' % ('::'.join(fqn)))
193 return
194
195
196 ##
197 ## We are facing a standard C++ class or struct
198 ##
199
200 isPublic = (child.kind == clang.cindex.CursorKind.STRUCT_DECL)
201
202 for i in child.get_children():
203 if (i.kind == clang.cindex.CursorKind.VISIBILITY_ATTR or # "default"
204 i.kind == clang.cindex.CursorKind.CXX_BASE_SPECIFIER): # base class
205 pass
206
207 elif i.kind == clang.cindex.CursorKind.CXX_ACCESS_SPEC_DECL:
208 isPublic = (i.access_specifier == clang.cindex.AccessSpecifier.PUBLIC)
209
210 elif (i.kind == clang.cindex.CursorKind.CLASS_DECL or
211 i.kind == clang.cindex.CursorKind.STRUCT_DECL):
212 # This is a subclass
213 if isPublic:
107 ExploreClass(i, fqn + [ i.spelling ]) 214 ExploreClass(i, fqn + [ i.spelling ])
108 215
109 elif (i.kind == clang.cindex.CursorKind.CXX_METHOD or 216 elif (i.kind == clang.cindex.CursorKind.CXX_METHOD or
110 i.kind == clang.cindex.CursorKind.CONSTRUCTOR or 217 i.kind == clang.cindex.CursorKind.CONSTRUCTOR or
111 i.kind == clang.cindex.CursorKind.DESTRUCTOR): 218 i.kind == clang.cindex.CursorKind.DESTRUCTOR):
112 if isPublic: 219 if isPublic:
113 hasImplementation = False 220 hasImplementation = False
114 for j in i.get_children(): 221 for j in i.get_children():
115 if j.kind == clang.cindex.CursorKind.COMPOUND_STMT: 222 if j.kind == clang.cindex.CursorKind.COMPOUND_STMT:
116 hasImplementation = True 223 hasImplementation = True
117 224
118 if hasImplementation: 225 if hasImplementation:
119 global FILES, COUNT 226 ReportProblem('Exported public method with an implementation', fqn, i)
120 FILES.append(os.path.normpath(str(child.location.file))) 227
121 COUNT += 1 228 elif i.kind == clang.cindex.CursorKind.VAR_DECL:
122 229 if isPublic:
123 print('Exported public method with an implementation: %s::%s()' % 230 ReportProblem('Exported public member variable', fqn, i)
124 ('::'.join(fqn), i.spelling)) 231
232 elif i.kind == clang.cindex.CursorKind.FUNCTION_TEMPLATE:
233 if isPublic:
234 ReportProblem('Exported public template method', fqn, i)
235
236 elif i.kind == clang.cindex.CursorKind.FRIEND_DECL:
237 if isPublic:
238 ReportProblem('Exported public friend method', fqn, i)
239
240 elif (i.kind == clang.cindex.CursorKind.TYPEDEF_DECL or # Allow "typedef"
241 i.kind == clang.cindex.CursorKind.ENUM_DECL): # Allow enums
242 pass
243
244 else:
245 if isPublic:
246 raise Exception('Unsupported: %s, %s' % (i.kind, i.location))
125 247
126 248
127 def ExploreNamespace(node, namespace): 249 def ExploreNamespace(node, namespace):
128 for child in node.get_children(): 250 for child in node.get_children():
129 fqn = namespace + [ child.spelling ] 251 fqn = namespace + [ child.spelling ]
133 255
134 elif (child.kind == clang.cindex.CursorKind.CLASS_DECL or 256 elif (child.kind == clang.cindex.CursorKind.CLASS_DECL or
135 child.kind == clang.cindex.CursorKind.STRUCT_DECL): 257 child.kind == clang.cindex.CursorKind.STRUCT_DECL):
136 ExploreClass(child, fqn) 258 ExploreClass(child, fqn)
137 259
260
261
262 print('')
263
138 for node in tu.cursor.get_children(): 264 for node in tu.cursor.get_children():
139 if (node.kind == clang.cindex.CursorKind.NAMESPACE and 265 if (node.kind == clang.cindex.CursorKind.NAMESPACE and
140 node.spelling == 'Orthanc'): 266 node.spelling == 'Orthanc'):
141 ExploreNamespace(node, [ 'Orthanc' ]) 267 ExploreNamespace(node, [ 'Orthanc' ])
142 268
143 269
144 print('\nTotal of possibly problematic methods: %d' % COUNT) 270 print('\nTotal of possibly problematic methods: %d' % COUNT)
145 271
146 print('\nFiles:\n') 272 print('\nProblematic files:\n')
147 for i in sorted(list(set(FILES))): 273 for i in sorted(list(set(FILES))):
148 print(i) 274 print(i)
149 275
276 print('')