Mercurial > hg > orthanc
view OrthancFramework/Resources/CheckOrthancFrameworkSymbols.py @ 5677:dc96401dbe88 find-refactoring
starting the refactoring of /tools/find
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 08 Jul 2024 19:03:23 +0200 |
parents | f7adfb22e20e |
children |
line wrap: on
line source
#!/usr/bin/env python # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium # Copyright (C) 2017-2023 Osimis S.A., Belgium # Copyright (C) 2024-2024 Orthanc Team SRL, Belgium # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation, either version 3 of # the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this program. If not, see # <http://www.gnu.org/licenses/>. ## ## This maintenance script detects all the public methods in the ## Orthanc framework that come with an inlined implementation in the ## header file. Such methods can break the ABI of the shared library, ## as the actual implementation might change over versions. ## # Ubuntu 20.04: # sudo apt-get install python-clang-6.0 # ./ParseWebAssemblyExports.py --libclang=libclang-6.0.so.1 ./Test.cpp # Ubuntu 18.04: # sudo apt-get install python-clang-4.0 # ./ParseWebAssemblyExports.py --libclang=libclang-4.0.so.1 ./Test.cpp # Ubuntu 14.04: # ./ParseWebAssemblyExports.py --libclang=libclang-3.6.so.1 ./Test.cpp import os import sys import clang.cindex import argparse ## ## Parse the command-line arguments ## parser = argparse.ArgumentParser(description = 'Parse WebAssembly C++ source file, and create a basic JavaScript wrapper.') parser.add_argument('--libclang', default = '', help = 'manually provides the path to the libclang shared library') parser.add_argument('--target-cpp-size', default = '', help = 'where to store C++ source to display the size of each public class') args = parser.parse_args() if len(args.libclang) != 0: clang.cindex.Config.set_library_file(args.libclang) index = clang.cindex.Index.create() ROOT = os.path.abspath(os.path.dirname(sys.argv[0])) SOURCES = [] for root, dirs, files in os.walk(os.path.join(ROOT, '..', 'Sources')): for name in files: if (os.path.splitext(name)[1] == '.h' and not name.endswith('.impl.h')): SOURCES.append(os.path.join(root, name)) AMALGAMATION = '/tmp/CheckOrthancFrameworkSymbols.cpp' with open(AMALGAMATION, 'w') as f: f.write('#include "%s"\n' % os.path.join(ROOT, '..', 'Sources', 'OrthancFramework.h')) for source in SOURCES: f.write('#include "%s"\n' % source) tu = index.parse(AMALGAMATION, [ '--std=c++11', '-DORTHANC_BUILDING_FRAMEWORK_LIBRARY=1', '-DORTHANC_BUILD_UNIT_TESTS=0', '-DORTHANC_ENABLE_BASE64=1', '-DORTHANC_ENABLE_CIVETWEB=1', '-DORTHANC_ENABLE_CURL=1', '-DORTHANC_ENABLE_DCMTK=1', '-DORTHANC_ENABLE_DCMTK_JPEG=1', '-DORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS=1', '-DORTHANC_ENABLE_DCMTK_NETWORKING=1', '-DORTHANC_ENABLE_DCMTK_TRANSCODING=1', '-DORTHANC_ENABLE_JPEG=1', '-DORTHANC_ENABLE_LOCALE=1', '-DORTHANC_ENABLE_LOGGING=1', '-DORTHANC_ENABLE_LOGGING_STDIO=0', '-DORTHANC_ENABLE_LUA=1', '-DORTHANC_ENABLE_MD5=1', '-DORTHANC_ENABLE_MONGOOSE=1', '-DORTHANC_ENABLE_PKCS11=1', '-DORTHANC_ENABLE_PNG=1', '-DORTHANC_ENABLE_PUGIXML=1', '-DORTHANC_ENABLE_SQLITE=1', '-DORTHANC_ENABLE_SSL=1', '-DORTHANC_ENABLE_ZLIB=1', '-DORTHANC_SANDBOXED=0', '-DORTHANC_SQLITE_STANDALONE=0', '-DORTHANC_SQLITE_VERSION=3027001', '-I/usr/include/jsoncpp', # On Ubuntu 18.04 '-I/usr/include/lua5.3', # On Ubuntu 18.04 ]) if len(tu.diagnostics) != 0: for d in tu.diagnostics: print(' ** %s' % d) print('') raise Exception('Error') FILES = [] COUNT = 0 ALL_TYPES = [] def ReportProblem(message, fqn, cursor): global FILES, COUNT FILES.append(os.path.normpath(str(cursor.location.file))) COUNT += 1 print('%s: %s::%s()' % (message, '::'.join(fqn), cursor.spelling)) def ExploreClass(child, fqn): # Safety check if (child.kind != clang.cindex.CursorKind.CLASS_DECL and child.kind != clang.cindex.CursorKind.STRUCT_DECL): raise Exception() # Ignore forward declaration of classes if not child.is_definition(): return ## ## Verify that the class is publicly exported (its visibility must ## be "default") ## visible = False for i in child.get_children(): if (i.kind == clang.cindex.CursorKind.VISIBILITY_ATTR and i.spelling == 'default'): visible = True if not visible: return global ALL_TYPES ALL_TYPES.append('::'.join(fqn)) ## ## Ignore pure abstract interfaces, by checking the following ## criteria: ## - It must be a C++ class (not a struct) ## - It must start with "I" ## - All its methods must be pure virtual (abstract) and public ## - Its destructor must be public, virtual, and must do nothing ## if (child.kind == clang.cindex.CursorKind.CLASS_DECL and child.spelling[0] == 'I' and child.spelling[1].isupper()): abstract = True isPublic = False for i in child.get_children(): if i.kind == clang.cindex.CursorKind.VISIBILITY_ATTR: # "default" pass elif i.kind == clang.cindex.CursorKind.CXX_ACCESS_SPEC_DECL: isPublic = (i.access_specifier == clang.cindex.AccessSpecifier.PUBLIC) elif i.kind == clang.cindex.CursorKind.CXX_BASE_SPECIFIER: if i.spelling != 'boost::noncopyable': abstract = False elif isPublic: if i.kind == clang.cindex.CursorKind.CXX_METHOD: if i.is_pure_virtual_method(): pass # pure virtual is ok elif i.is_static_method(): # static method without an inline implementation is ok for j in i.get_children(): if j.kind == clang.cindex.CursorKind.COMPOUND_STMT: abstract = False else: abstract = False elif (i.kind == clang.cindex.CursorKind.DESTRUCTOR and i.is_virtual_method()): # The destructor must be virtual, and must do nothing c = list(i.get_children()) if (len(c) != 1 or c[0].kind != clang.cindex.CursorKind.COMPOUND_STMT or len(list(c[0].get_children())) != 0): abstract = False elif i.kind == clang.cindex.CursorKind.CLASS_DECL: ExploreClass(i, fqn + [ i.spelling ]) elif (i.kind == clang.cindex.CursorKind.TYPEDEF_DECL or # Allow "typedef" i.kind == clang.cindex.CursorKind.ENUM_DECL): # Allow enums pass else: abstract = False if abstract: print('Detected a pure interface (this is fine): %s' % ('::'.join(fqn))) else: ReportProblem('Not a pure interface', fqn, child) return ## ## We are facing a standard C++ class or struct ## isPublic = (child.kind == clang.cindex.CursorKind.STRUCT_DECL) membersCount = 0 membersSize = 0 for i in child.get_children(): if (i.kind == clang.cindex.CursorKind.VISIBILITY_ATTR or # "default" i.kind == clang.cindex.CursorKind.CXX_BASE_SPECIFIER): # base class pass elif i.kind == clang.cindex.CursorKind.CXX_ACCESS_SPEC_DECL: isPublic = (i.access_specifier == clang.cindex.AccessSpecifier.PUBLIC) elif i.kind == clang.cindex.CursorKind.CLASS_DECL: # This is a subclass if isPublic: ExploreClass(i, fqn + [ i.spelling ]) elif (i.kind == clang.cindex.CursorKind.CXX_METHOD or i.kind == clang.cindex.CursorKind.CONSTRUCTOR or i.kind == clang.cindex.CursorKind.DESTRUCTOR): if isPublic: hasImplementation = False for j in i.get_children(): if j.kind == clang.cindex.CursorKind.COMPOUND_STMT: hasImplementation = True if hasImplementation: ReportProblem('Exported public method with an implementation', fqn, i) elif i.kind == clang.cindex.CursorKind.VAR_DECL: raise Exception('Unsupported: %s, %s' % (i.kind, i.location)) elif i.kind == clang.cindex.CursorKind.FUNCTION_TEMPLATE: # An inline function template is OK, as it is not added to # a shared library, but compiled by the client of the library if isPublic: print('Detected a template function (this is fine, but avoid it as much as possible): %s' % ('::'.join(fqn + [ i.spelling ]))) hasImplementation = False for j in i.get_children(): if j.kind == clang.cindex.CursorKind.COMPOUND_STMT: hasImplementation = True if not hasImplementation: ReportProblem('Exported template function without an inline implementation', fqn, i) elif (i.kind == clang.cindex.CursorKind.TYPEDEF_DECL or # Allow "typedef" i.kind == clang.cindex.CursorKind.ENUM_DECL): # Allow enums pass elif i.kind == clang.cindex.CursorKind.FRIEND_DECL: children = list(i.get_children()) if (isPublic and (len(children) != 1 or not children[0].displayname in [ # This is supported for ABI compatibility with Orthanc <= 1.8.0 'operator<<(std::ostream &, const Orthanc::DicomTag &)', ])): raise Exception('Unsupported: %s, %s' % (i.kind, i.location)) elif i.kind == clang.cindex.CursorKind.FIELD_DECL: # TODO if i.type.get_size() > 0: membersSize += i.type.get_size() membersCount += 1 else: if isPublic: raise Exception('Unsupported: %s, %s' % (i.kind, i.location)) #print('Size of %s => (%d,%d)' % ('::'.join(fqn), membersCount, membersSize)) def ExploreNamespace(node, namespace): for child in node.get_children(): fqn = namespace + [ child.spelling ] if child.kind == clang.cindex.CursorKind.NAMESPACE: ExploreNamespace(child, fqn) elif (child.kind == clang.cindex.CursorKind.CLASS_DECL or child.kind == clang.cindex.CursorKind.STRUCT_DECL): ExploreClass(child, fqn) elif child.kind == clang.cindex.CursorKind.FUNCTION_DECL: visible = False hasImplementation = False for i in child.get_children(): if (i.kind == clang.cindex.CursorKind.VISIBILITY_ATTR and i.spelling == 'default'): visible = True elif i.kind == clang.cindex.CursorKind.COMPOUND_STMT: hasImplementation = True if visible and hasImplementation: ReportProblem('Exported public function with an implementation', fqn, i) print('') for node in tu.cursor.get_children(): if (node.kind == clang.cindex.CursorKind.NAMESPACE and node.spelling == 'Orthanc'): ExploreNamespace(node, [ 'Orthanc' ]) if args.target_cpp_size != '': with open(args.target_cpp_size, 'w') as f: for t in sorted(ALL_TYPES): f.write(' printf("sizeof(::%s) == %%d\\n", static_cast<int>(sizeof(::%s)));\n' % (t, t)) print('\nTotal of possibly problematic methods: %d' % COUNT) print('\nProblematic files:\n') for i in sorted(list(set(FILES))): print(i) print('')