macOS Mojave 10.14.2 llvm 7.0.1
1 2 3 4 5 6 7 |
$ brew install llvm --with-clang --with-python --with-lld $ export DYLD_LIBRARY_PATH=/usr/local/opt/llvm/lib $ export LLVM_VER=$(brew list --versions | grep llvm | sed 's/\([0-9]\).*/\1/g' | tr -cd "[0-9]") $ pip install clang |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#!/usr/bin/env python # -*- coding: utf-8 -*- """ Usage: call with <filename> <typename> """ import sys import clang.cindex # 也可以使用如下命令指定libclang的加载位置,这个动态库可以直接拷贝到其他机器上使用,是个独立库 # clang.cindex.Config.set_library_path('/usr/local/opt/llvm/lib') # 如下可以简化代码 # from clang.cindex import CursorKind # from clang.cindex import AccessSpecifier def find_typerefs(node, typename): """ Find all references to the type named 'typename' """ if node.kind.is_reference(): ref_node = node.get_definition() if ref_node : if ref_node.spelling == typename: print 'Found %s [line=%s, col=%s]' % ( typename, node.location.line, node.location.column) # Recurse for children of this node for c in node.get_children(): find_typerefs(c, typename) index = clang.cindex.Index.create() tu = index.parse(sys.argv[1]) print 'Translation unit:', tu.spelling find_typerefs(tu.cursor, sys.argv[2]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
#!/usr/bin/env python # -*- coding: utf-8 -*- """ C++ linter using libclang. Call with [filenames] """ from __future__ import print_function from clang.cindex import Config, TypeKind, CursorKind, Index from pprint import pprint import platform import sys if platform.system() == "Linux": Config.set_library_file("/usr/lib/llvm-3.4/lib/libclang.so") else: Config.set_library_file("/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib") def error(out_errors, node, msg): error_msg = "{}:{}: {}".format(node.location.file, node.location.line, msg) out_errors.append(error_msg) print(error_msg, file=sys.stderr) def is_mut_ref(arg_type): if arg_type.kind in [TypeKind.POINTER, TypeKind.LVALUEREFERENCE, TypeKind.INCOMPLETEARRAY, TypeKind.CONSTANTARRAY]: if arg_type.kind in [TypeKind.POINTER, TypeKind.LVALUEREFERENCE]: pointee_type = arg_type.get_pointee() else: pointee_type = arg_type.get_array_element_type() # print("pointee_type.kind: {}".format(pointee_type.kind)) # print("pointee_type.is_const_qualified(): {}".format(pointee_type.is_const_qualified())) if not pointee_type.is_const_qualified(): return True return False def check_argument(out_errors, function, node, function_parse_progress): assert node.kind == CursorKind.PARM_DECL if function.kind == CursorKind.FUNCTION_DECL and function.spelling == "main": # Ignore main function return # print("") # print("node.spelling: {}".format(node.spelling)) # print("node.type.kind: {}".format(node.type.kind)) # print("node.type.get_ref_qualifier(): {}".format(node.type.get_ref_qualifier())) # print("node.type.is_const_qualified(): {}".format(node.type.is_const_qualified())) # pprint(dir(node)) name = node.spelling if not name: # Ignore nameless arguments (e.g. Foo(Foo&)) return if is_mut_ref(node.type): if name.startswith("o_"): function_parse_progress["state"] = "out" elif name.startswith("io_"): if function_parse_progress["state"] != "io": error(out_errors, node, "io_ arguments should be first") function_parse_progress["state"] = "io" else: error(out_errors, node, "Non-const reference/pointer/array argument should be prefixed with " \ "either o_ (for out) or io_ (for in-out), e.g. 'o_{}'".format(name)) else: if function_parse_progress["state"] == "out": error(out_errors, node, "input arguments should come before output arguments") function_parse_progress["state"] = "in" def do_lint(out_errors, node, root_file): if node.location.file and node.location.file.name != root_file.name: # This is ugly, but works. return # print("{}:{} node.kind: {}".format(node.location.file, node.location.line, node.kind)) # print("node.translation_unit: {}".format(node.translation_unit)) # # pprint(dir(node.translation_unit)) # print("node.location.file: {}".format(node.location.file)) # pprint(dir(node.location)) # exit() # CursorKind.CONSTRUCTOR excluded: references there are often stored, so not o_ or io_ if node.kind in [CursorKind.FUNCTION_DECL, CursorKind.CXX_METHOD]: # print("Found a function!") # print("node.spelling: {}".format(node.spelling)) # print("node.displayname: {}".format(node.displayname)) function_parse_progress = { "state": "io", # "io", "in" or "out" } for arg in node.get_arguments(): check_argument(out_errors, node, arg, function_parse_progress) # Recurse for children of this node for c in node.get_children(): do_lint(out_errors, c, root_file) def lint_file(filepath): index = Index.create() tu = index.parse(filepath) root_file = tu.get_file(tu.spelling) errors = [] do_lint(errors, tu.cursor, root_file) return errors def main(): if len(sys.argv) == 1: print("Usage: {} [filenames]".format(sys.argv[0])) return for filepath in sys.argv[1:]: lint_file(filepath) if __name__ == "__main__": main() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
#!/usr/bin/env python import clang.cindex import sys import os import logging import argparse import fnmatch from DotGenerator import * index = clang.cindex.Index.create() dotGenerator = DotGenerator() def findFilesInDir(rootDir, patterns): """ Searches for files in rootDir which file names mathes the given pattern. Returns a list of file paths of found files""" foundFiles = [] for root, dirs, files in os.walk(rootDir): for p in patterns: for filename in fnmatch.filter(files, p): foundFiles.append(os.path.join(root, filename)) return foundFiles def processClassField(cursor): """ Returns the name and the type of the given class field. The cursor must be of kind CursorKind.FIELD_DECL""" type = None fieldChilds = list(cursor.get_children()) if len(fieldChilds) == 0: # if there are not cursorchildren, the type is some primitive datatype type = cursor.type.spelling else: # if there are cursorchildren, the type is some non-primitive datatype (a class or class template) for cc in fieldChilds: if cc.kind == clang.cindex.CursorKind.TEMPLATE_REF: type = cc.spelling elif cc.kind == clang.cindex.CursorKind.TYPE_REF: type = cursor.type.spelling name = cursor.spelling return name, type def processClassMemberDeclaration(umlClass, cursor): """ Processes a cursor corresponding to a class member declaration and appends the extracted information to the given umlClass """ if cursor.kind == clang.cindex.CursorKind.CXX_BASE_SPECIFIER: for baseClass in cursor.get_children(): if baseClass.kind == clang.cindex.CursorKind.TEMPLATE_REF: umlClass.parents.append(baseClass.spelling) elif baseClass.kind == clang.cindex.CursorKind.TYPE_REF: umlClass.parents.append(baseClass.type.spelling) elif cursor.kind == clang.cindex.CursorKind.FIELD_DECL: # non static data member name, type = processClassField(cursor) if name is not None and type is not None: # clang < 3.5: needs patched cindex.py to have # clang.cindex.AccessSpecifier available: # https://gitorious.org/clang-mirror/clang-mirror/commit/e3d4e7c9a45ed9ad4645e4dc9f4d3b4109389cb7 if cursor.access_specifier == clang.cindex.AccessSpecifier.PUBLIC: umlClass.publicFields.append((name, type)) elif cursor.access_specifier == clang.cindex.AccessSpecifier.PRIVATE: umlClass.privateFields.append((name, type)) elif cursor.access_specifier == clang.cindex.AccessSpecifier.PROTECTED: umlClass.protectedFields.append((name, type)) elif cursor.kind == clang.cindex.CursorKind.CXX_METHOD: try: returnType, argumentTypes = cursor.type.spelling.split(' ', 1) if cursor.access_specifier == clang.cindex.AccessSpecifier.PUBLIC: umlClass.publicMethods.append((returnType, cursor.spelling, argumentTypes)) elif cursor.access_specifier == clang.cindex.AccessSpecifier.PRIVATE: umlClass.privateMethods.append((returnType, cursor.spelling, argumentTypes)) elif cursor.access_specifier == clang.cindex.AccessSpecifier.PROTECTED: umlClass.protectedMethods.append((returnType, cursor.spelling, argumentTypes)) except: logging.error("Invalid CXX_METHOD declaration! " + str(cursor.type.spelling)) elif cursor.kind == clang.cindex.CursorKind.FUNCTION_TEMPLATE: returnType, argumentTypes = cursor.type.spelling.split(' ', 1) if cursor.access_specifier == clang.cindex.AccessSpecifier.PUBLIC: umlClass.publicMethods.append((returnType, cursor.spelling, argumentTypes)) elif cursor.access_specifier == clang.cindex.AccessSpecifier.PRIVATE: umlClass.privateMethods.append((returnType, cursor.spelling, argumentTypes)) elif cursor.access_specifier == clang.cindex.AccessSpecifier.PROTECTED: umlClass.protectedMethods.append((returnType, cursor.spelling, argumentTypes)) def processClass(cursor, inclusionConfig): """ Processes an ast node that is a class. """ umlClass = UmlClass() # umlClass is the datastructure for the DotGenerator # that stores the necessary information about a single class. # We extract this information from the clang ast hereafter ... if cursor.kind == clang.cindex.CursorKind.CLASS_TEMPLATE: # process declarations like: # template <typename T> class MyClass umlClass.fqn = cursor.spelling else: # process declarations like: # class MyClass ... # struct MyStruct ... umlClass.fqn = cursor.type.spelling # the fully qualified name import re if (inclusionConfig['excludeClasses'] and re.match(inclusionConfig['excludeClasses'], umlClass.fqn)): return if (inclusionConfig['includeClasses'] and not re.match(inclusionConfig['includeClasses'], umlClass.fqn)): return for c in cursor.get_children(): # process member variables and methods declarations processClassMemberDeclaration(umlClass, c) dotGenerator.addClass(umlClass) def traverseAst(cursor, inclusionConfig): if (cursor.kind == clang.cindex.CursorKind.CLASS_DECL or cursor.kind == clang.cindex.CursorKind.STRUCT_DECL or cursor.kind == clang.cindex.CursorKind.CLASS_TEMPLATE): # if the current cursor is a class, class template or struct declaration, # we process it further ... processClass(cursor, inclusionConfig) for child_node in cursor.get_children(): traverseAst(child_node, inclusionConfig) def parseTranslationUnit(filePath, includeDirs, inclusionConfig): clangArgs = ['-x', 'c++'] + ['-I' + includeDir for includeDir in includeDirs] tu = index.parse(filePath, args=clangArgs, options=clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES) for diagnostic in tu.diagnostics: logging.debug(diagnostic) logging.info('Translation unit:' + tu.spelling + "\n") traverseAst(tu.cursor, inclusionConfig) if __name__ == "__main__": parser = argparse.ArgumentParser(description="CodeDependencyVisualizer (CDV)") parser.add_argument('-d', required=True, help="directory with source files to parse (searches recusively)") parser.add_argument('-o', '--outFile', default='uml.dot', help="output file name / name of generated dot file") parser.add_argument('-u', '--withUnusedHeaders', help="parse unused header files (slow)") parser.add_argument('-a', '--associations', action="store_true", help="draw class member assiciations") parser.add_argument('-i', '--inheritances', action="store_true", help="draw class inheritances") parser.add_argument('-p', '--privMembers', action="store_true", help="show private members") parser.add_argument('-t', '--protMembers', action="store_true", help="show protected members") parser.add_argument('-P', '--pubMembers', action="store_true", help="show public members") parser.add_argument('-I', '--includeDirs', help="additional search path(s) for include files (seperated by space)", nargs='+') parser.add_argument('-v', '--verbose', action="store_true", help="print verbose information for debugging purposes") parser.add_argument('--excludeClasses', help="classes matching this pattern will be excluded") parser.add_argument('--includeClasses', help="only classes matching this pattern will be included") args = vars(parser.parse_args(sys.argv[1:])) filesToParsePatterns = ['*.cpp', '*.cxx', '*.c', '*.cc'] if args['withUnusedHeaders']: filesToParsePatterns += ['*.h', '*.hxx', '*.hpp'] filesToParse = findFilesInDir(args['d'], filesToParsePatterns) subdirectories = [x[0] for x in os.walk(args['d'])] loggingFormat = "%(levelname)s - %(module)s: %(message)s" logging.basicConfig(format=loggingFormat, level=logging.INFO) if args['verbose']: logging.basicConfig(format=loggingFormat, level=logging.DEBUG) logging.info("found " + str(len(filesToParse)) + " source files.") for sourceFile in filesToParse: logging.info("parsing file " + sourceFile) parseTranslationUnit(sourceFile, args['includeDirs'], { 'excludeClasses': args['excludeClasses'], 'includeClasses': args['includeClasses']}) dotGenerator.setDrawAssociations(args['associations']) dotGenerator.setDrawInheritances(args['inheritances']) dotGenerator.setShowPrivMethods(args['privMembers']) dotGenerator.setShowProtMethods(args['protMembers']) dotGenerator.setShowPubMethods(args['pubMembers']) dotfileName = args['outFile'] logging.info("generating dotfile " + dotfileName) with open(dotfileName, 'w') as dotfile: dotfile.write(dotGenerator.generate()) |
参考链接
- 【clang】高效开发一个clang plugin
- Parsing C++ in Python with Clang
- How to solve the loading error of clang's Python binding?
- Using libclang to parse in C++ in Python
- Cannot run libclang: error ''specified module could not be found''
- (原创) clang的python接口(一)
- Implementing a code generator with libclang
- gklingler/CodeDependencyVisualizer
- How to get function definition/signature as a string in Clang?
- AST of a project by Clang
- libigl/libigl