diff --git a/dev-scripts/miniAstDumper.py b/dev-scripts/miniAstDumper.py index dee7354..4357765 100755 --- a/dev-scripts/miniAstDumper.py +++ b/dev-scripts/miniAstDumper.py @@ -1,172 +1,222 @@ #!/usr/bin/env python import cbor, sys, time, os class SourceLocation: def __init__(self): self.filename = "" self.lineNumber = -1 self.columnNumber = -1 def asString(self): return self.filename + ":" + str(self.lineNumber) + ":" + str(self.columnNumber) def dump(self): print(self.asString()) def check_sanity(self): if self.lineNumber < 0 or self.columnNumber < 0: print("SourceLocation::check_sanity: loc numbers are invalid! " + self.lineNumber + " ; " + self.columnNumber) if not self.filename: print("SourceLocation::check_sanity: loc filename is invalid!") class FunctionCall: def __init__(self): self.callee_id = -1 self.loc_start = SourceLocation() def check_sanity(self): if self.callee_id == -1: print("FunctionCall::check_sanity: callee_id is -1!") self.loc_start.check_sanity() -class CXXMethod: +class Function: def __init__(self): self.id = 0 self.qualified_name = "" - self.method_flags = 0 def check_sanity(self): if self.id == -1: print("CXXMethod::check_sanity: id is -1!") if not self.qualified_name: print("CXXMethod::check_sanity: qualified_name is empty!") +class CXXMethod(Function): + def __init__(self): + Function.__init__(self) + self.method_flags = 0 + class CXXClass: def __init__(self): # self.id = -1 self.qualified_name = "" self.methods = [] self.class_flags = 0 def check_sanity(self): #if self.id == -1: # print("CXXClass::check_sanity: id is -1!") if not self.qualified_name: print("CXXClass::check_sanity: qualified_name is empty!") for m in self.methods: m.check_sanity() class GlobalAST: def __init__(self): self.cxx_classes = [] + self.functions = [] self.function_calls = [] def check_sanity(self): for c in self.cxx_classes: c.check_sanity() for f in self.function_calls: f.check_sanity() _globalAST = GlobalAST() +_next_function_id = 1 +def next_function_id(): + global _next_function_id + result = _next_function_id + _next_function_id += 1 + return result + def parse_loc(cborLoc, file_map, tu_cwd): loc = SourceLocation() loc.filename = file_map[str(cborLoc['fileId'])] # Make absolute if not loc.filename.startswith('/'): # TODO windows loc.filename = tu_cwd + '/' + loc.filename # Normalize loc.filename = os.path.normpath(loc.filename) loc.lineNumber = cborLoc['line'] loc.columnNumber = cborLoc['column'] return loc def read_file(filename): f = open(filename, 'rb') contents = f.read() f.close() return contents def read_cbor(filename): contents = read_file(filename); return cbor.loads(contents) def load_cbor(filename, globalAST): cborData = read_cbor(filename) file_map = {} current_tu_cwd = cborData['cwd'] + tu_function_map = {} + + # populate the file map if 'files' in cborData: for fileId in cborData['files'].keys(): file_map[fileId] = cborData['files'][fileId] if 'stuff' in cborData: + # Process classes and methods for stuff in cborData['stuff']: if 'type' in stuff: if stuff['type'] == 31: # CXXRecordDecl cxxclass = CXXClass() # cxxclass.id = stuff['id'] cxxclass.qualified_name = stuff['name'] if 'methods' in stuff: for m in stuff['methods']: method = CXXMethod() - method.id = m['id'] + method.id = next_function_id() # Attribute a sequential id, that's unique across TUs + method.qualified_name = m['name'] cxxclass.methods.append(method) + local_id_in_tu = m['id'] + + if local_id_in_tu in tu_function_map.keys(): + print('method is duplicated! ' + method.qualified_name) + tu_function_map[local_id_in_tu] = method + globalAST.cxx_classes.append(cxxclass) - elif stuff['type'] == 48: # CallExpr - funccall = FunctionCall() - funccall.callee_id = stuff['calleeId'] - funccall.loc_start = parse_loc(stuff['loc'], file_map, current_tu_cwd) + + if stuff['type'] == 48: # FunctionDecl + func = Function() + func.id = next_function_id() # Attribute a sequential id, that's unique across TUs + func.qualified_name = stuff['name'] + globalAST.functions.append(func) + local_id_in_tu = stuff['id'] + if local_id_in_tu in tu_function_map.keys(): + print('function is duplicated! ' + method.qualified_name) + tu_function_map[local_id_in_tu] = func + + if func.qualified_name == 'qBound': + print("qBound has id=" + str(local_id_in_tu)) + + + # Process CallExprs + for stuff in cborData['stuff']: + if 'stmt_type' in stuff and stuff['stmt_type'] == 48: # CallExpr + funccall = FunctionCall() + local_callee_id_in_tu = stuff['calleeId'] + source_loc = parse_loc(stuff['loc'], file_map, current_tu_cwd); + + if local_callee_id_in_tu not in tu_function_map.keys(): + print("Could not find function with local tu id=" + str(local_callee_id_in_tu) + ", loc=" + source_loc.asString()) + else: + method = tu_function_map[local_callee_id_in_tu] + funccall.callee_id = method.id + funccall.loc_start = source_loc globalAST.function_calls.append(funccall) #def get_class_by_name(qualified_name): # result = [] # for c in _globalAST.cxx_classes: # if c.qualified_name == qualified_name: # result.append(c) # return result #def get_calls_by_name(callee_name): # result = [] # for f in _globalAST.function_calls: #if f.callee_name == callee_name: # result.append(f) #return result load_cbor(sys.argv[1], _globalAST) -_globalAST.check_sanity() + +print ("Functions: " + str(len(_globalAST.functions))) + +#_globalAST.check_sanity() #string_class = get_class_by_name("QString")[0] #for f in get_calls_by_name("QObject::connect"): #print(f.loc_start.dump()) #for f in _globalAST.function_calls: # print(f.callee_name) #for m in string_class.methods: # print(m.qualified_name) #for c in _globalAST.cxx_classes: # print(c.qualified_name) #print(cborData) diff --git a/src/MiniAstDumper.cpp b/src/MiniAstDumper.cpp index 9c889c7..c6ddaaf 100644 --- a/src/MiniAstDumper.cpp +++ b/src/MiniAstDumper.cpp @@ -1,312 +1,353 @@ /* This file is part of the clazy static checker. Copyright (C) 2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Sérgio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "MiniAstDumper.h" #include "SourceCompatibilityHelpers.h" #include "clazy_stl.h" #include "StringUtils.h" #include "QtUtils.h" #include #include #include #include #include using namespace clang; using namespace std; MiniAstDumperASTAction::MiniAstDumperASTAction() { } bool MiniAstDumperASTAction::ParseArgs(const CompilerInstance &, const std::vector &) { return true; } std::unique_ptr MiniAstDumperASTAction::CreateASTConsumer(CompilerInstance &ci, llvm::StringRef) { return std::unique_ptr(new MiniASTDumperConsumer(ci)); } MiniASTDumperConsumer::MiniASTDumperConsumer(CompilerInstance &ci) : m_ci(ci) { auto &sm = m_ci.getASTContext().getSourceManager(); const FileEntry *fileEntry = sm.getFileEntryForID(sm.getMainFileID()); m_currentCppFile = fileEntry->getName(); m_cborBuf = reinterpret_cast(malloc(m_bufferSize)); cbor_encoder_init(&m_cborEncoder, m_cborBuf, m_bufferSize, 0); cborCreateMap(&m_cborEncoder, &m_cborRootMapEncoder, 4); cborEncodeString(m_cborRootMapEncoder, "tu"); cborEncodeString(m_cborRootMapEncoder, m_currentCppFile.c_str()); char cwd[4096]; // TODO: Just use std::filesystem::current_path cborEncodeString(m_cborRootMapEncoder, "cwd"); cborEncodeString(m_cborRootMapEncoder, std::string(getcwd(cwd, sizeof(cwd))).c_str()); cborEncodeString(m_cborRootMapEncoder, "stuff"); cborCreateArray(&m_cborRootMapEncoder, &m_cborStuffArray, CborIndefiniteLength); } MiniASTDumperConsumer::~MiniASTDumperConsumer() { cborCloseContainer(&m_cborRootMapEncoder, &m_cborStuffArray); cborEncodeString(m_cborRootMapEncoder, "files"); dumpFileMap(&m_cborRootMapEncoder); cborCloseContainer(&m_cborEncoder, &m_cborRootMapEncoder); size_t size = cbor_encoder_get_buffer_size(&m_cborEncoder, m_cborBuf); const std::string cborFileName = m_currentCppFile + ".cbor"; std::ofstream myFile(cborFileName, std::ios::out | ios::binary); myFile.write(reinterpret_cast(m_cborBuf), long(size)); delete m_cborBuf; } bool MiniASTDumperConsumer::VisitDecl(Decl *decl) { if (auto tsd = dyn_cast(decl)) { - //llvm::errs() << "ClassTemplateSpecializationDecl: " + tsd->getQualifiedNameAsString() + "\n"; + // llvm::errs() << "ClassTemplateSpecializationDecl: " + tsd->getQualifiedNameAsString() + "\n"; } else if (auto rec = dyn_cast(decl)) { if (!rec->isThisDeclarationADefinition()) { // No forward-declarations return true; } if (rec->getDescribedClassTemplate()) { // This is a template. We'll rather print it's specializations when catching ClassTemplateDecl return true; } dumpCXXRecordDecl(rec, &m_cborStuffArray); } else if (auto ctd = dyn_cast(decl)) { /*llvm::errs() << "Found template: " << ctd->getNameAsString() << "; this=" << ctd - << "\n";*/ + << "\n"; for (auto s : ctd->specializations()) { - /* llvm::errs() << "Found specialization: " << s->getQualifiedNameAsString() << "\n"; + llvm::errs() << "Found specialization: " << s->getQualifiedNameAsString() << "\n"; auto &args = s->getTemplateArgs(); const unsigned int count = args.size(); for (unsigned int i = 0; i < count; ++i) { args.get(i).print(PrintingPolicy({}), llvm::errs()); llvm::errs() << "\n"; - }*/ + } + } */ + } else if (auto func = dyn_cast(decl)) { + if (isa(decl)) // Methods are handled when processing the class + return true; + + if (func->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate) { + // Already handled when catching FunctionTemplateDecl. When we write func->getTemplatedDecl(). + return true; + } + + dumpFunctionDecl(func, &m_cborStuffArray); + } else if (auto func = dyn_cast(decl)) { + + dumpFunctionDecl(func->getTemplatedDecl(), &m_cborStuffArray); + + for (auto s : func->specializations()) { + dumpFunctionDecl(s, &m_cborStuffArray); } } return true; } bool MiniASTDumperConsumer::VisitStmt(Stmt *stmt) { if (auto callExpr = dyn_cast(stmt)) { dumpCallExpr(callExpr, &m_cborStuffArray); } return true; } void MiniASTDumperConsumer::HandleTranslationUnit(ASTContext &ctx) { TraverseDecl(ctx.getTranslationUnitDecl()); } void MiniASTDumperConsumer::dumpCXXMethodDecl(CXXMethodDecl *method, CborEncoder *encoder) { CborEncoder recordMap; cborCreateMap(encoder, &recordMap, 2); cborEncodeString(recordMap, "name"); cborEncodeString(recordMap, method->getQualifiedNameAsString().c_str()); cborEncodeString(recordMap, "id"); cborEncodeInt(recordMap, int64_t(method)); cborCloseContainer(encoder, &recordMap); } +void MiniASTDumperConsumer::dumpFunctionDecl(FunctionDecl *func, CborEncoder *encoder) +{ + CborEncoder recordMap; + cborCreateMap(encoder, &recordMap, 3); + + cborEncodeString(recordMap, "name"); + cborEncodeString(recordMap, func->getQualifiedNameAsString().c_str()); + + cborEncodeString(recordMap, "type"); + cborEncodeInt(recordMap, func->FunctionDecl::getDeclKind()); + + cborEncodeString(recordMap, "id"); + cborEncodeInt(recordMap, int64_t(func)); + + cborCloseContainer(encoder, &recordMap); +} + void MiniASTDumperConsumer::dumpCXXRecordDecl(CXXRecordDecl *rec, CborEncoder *encoder) { if (rec->isUnion()) return; CborEncoder recordMap; cborCreateMap(encoder, &recordMap, CborIndefiniteLength); cborEncodeString(recordMap, "type"); cborEncodeInt(recordMap, rec->getDeclKind()); cborEncodeString(recordMap, "name"); cborEncodeString(recordMap, rec->getQualifiedNameAsString().c_str()); cborEncodeString(recordMap, "loc"); const SourceLocation loc = clazy::getLocStart(rec); dumpLocation(loc, &recordMap); if (clazy::isQObject(rec)) { // TODO: Use flags cborEncodeString(recordMap, "isQObject"); cborEncodeBool(recordMap, true); } cborEncodeString(recordMap, "methods"); CborEncoder cborMethodList; cborCreateArray(&recordMap, &cborMethodList, CborIndefiniteLength); for (auto method : rec->methods()) { dumpCXXMethodDecl(method, &cborMethodList); } + cborCloseContainer(&recordMap, &cborMethodList); cborCloseContainer(&m_cborStuffArray, &recordMap); // Sanity: if (rec->getQualifiedNameAsString().empty()) { llvm::errs() << "Record has no name. loc=" << loc.printToString(m_ci.getSourceManager()) << "\n"; } } void MiniASTDumperConsumer::dumpCallExpr(CallExpr *callExpr, CborEncoder *encoder) { FunctionDecl *func = callExpr->getDirectCallee(); if (!func || !func->getDeclName().isIdentifier()) return; + const bool isBuiltin = func->getBuiltinID() != 0; + if (isBuiltin) //We don't need them now + return; + + func = func->getCanonicalDecl(); + CborEncoder callMap; cborCreateMap(encoder, &callMap, 3); - cborEncodeString(callMap, "type"); + cborEncodeString(callMap, "stmt_type"); cborEncodeInt(callMap, callExpr->getStmtClass()); cborEncodeString(callMap, "calleeId"); cborEncodeInt(callMap, int64_t(func)); cborEncodeString(callMap, "loc"); dumpLocation(clazy::getLocStart(callExpr), &callMap); cborCloseContainer(encoder, &callMap); } void MiniASTDumperConsumer::dumpLocation(SourceLocation loc, CborEncoder *encoder) { CborEncoder locMap; cborCreateMap(encoder, &locMap, CborIndefiniteLength); auto &sm = m_ci.getSourceManager(); if (loc.isMacroID()) { /// The place where the macro is defined: SourceLocation spellingLoc = sm.getSpellingLoc(loc); const FileID fileId = sm.getFileID(spellingLoc); m_fileIds[fileId.getHashValue()] = sm.getFilename(spellingLoc).str(); cborEncodeString(locMap, "spellingFileId"); cborEncodeInt(locMap, fileId.getHashValue()); cborEncodeString(locMap, "spellingLine"); cborEncodeInt(locMap, sm.getSpellingLineNumber(loc)); cborEncodeString(locMap, "spellingColumn"); cborEncodeInt(locMap, sm.getSpellingColumnNumber(loc)); /// Get the place where the macro is used: loc = sm.getExpansionLoc(loc); } const FileID fileId = sm.getFileID(loc); m_fileIds[fileId.getHashValue()] = sm.getFilename(loc).str(); if (sm.getFilename(loc).empty()) { // Shouldn't happen llvm::errs() << "Invalid filename for " << loc.printToString(sm) << "\n"; } cborEncodeString(locMap, "fileId"); cborEncodeInt(locMap, fileId.getHashValue()); auto ploc = sm.getPresumedLoc(loc); cborEncodeString(locMap, "line"); cborEncodeInt(locMap, ploc.getLine()); cborEncodeString(locMap, "column"); cborEncodeInt(locMap, ploc.getColumn()); cborCloseContainer(encoder, &locMap); } void MiniASTDumperConsumer::dumpFileMap(CborEncoder *encoder) { CborEncoder fileMap; cborCreateMap(encoder, &fileMap, m_fileIds.size()); for (auto it : m_fileIds) { cborEncodeString(fileMap, std::to_string(it.first).c_str()); cborEncodeString(fileMap, it.second.c_str()); } cborCloseContainer(encoder, &fileMap); } void MiniASTDumperConsumer::cborEncodeString(CborEncoder &enc, const char *str) { if (cbor_encode_text_stringz(&enc, str) != CborNoError) llvm::errs() << "cborEncodeString error\n"; } void MiniASTDumperConsumer::cborEncodeInt(CborEncoder &enc, int64_t v) { if (cbor_encode_int(&enc, v) != CborNoError) llvm::errs() << "cborEncodeInt error\n"; } void MiniASTDumperConsumer::cborEncodeBool(CborEncoder &enc, bool b) { if (cbor_encode_boolean(&enc, b) != CborNoError) llvm::errs() << "cborEncodeBool error\n"; } void MiniASTDumperConsumer::cborCreateMap(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length) { if (cbor_encoder_create_map(encoder, mapEncoder, length) != CborNoError) llvm::errs() << "cborCreateMap error\n"; } void MiniASTDumperConsumer::cborCreateArray(CborEncoder *encoder, CborEncoder *arrayEncoder, size_t length) { if (cbor_encoder_create_array(encoder, arrayEncoder, length) != CborNoError) llvm::errs() << "cborCreateMap error\n"; } void MiniASTDumperConsumer::cborCloseContainer(CborEncoder *encoder, const CborEncoder *containerEncoder) { if (cbor_encoder_close_container(encoder, containerEncoder) != CborNoError) llvm::errs() << "cborCloseContainer error\n"; } static FrontendPluginRegistry::Add X2("clazyMiniAstDumper", "Clazy Mini AST Dumper plugin"); diff --git a/src/MiniAstDumper.h b/src/MiniAstDumper.h index 53ac3f3..3a1cd85 100644 --- a/src/MiniAstDumper.h +++ b/src/MiniAstDumper.h @@ -1,91 +1,92 @@ /* This file is part of the clazy static checker. Copyright (C) 2019 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Sérgio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CLAZY_MINI_AST_DUMPER #define CLAZY_MINI_AST_DUMPER #include "cbor.h" #include #include #include #include #include #include #include #include namespace clang { class CompilerInstance; class ASTContext; class Decl; class Stmt; } class MiniAstDumperASTAction : public clang::PluginASTAction { public: MiniAstDumperASTAction(); protected: bool ParseArgs(const clang::CompilerInstance &ci, const std::vector &args_) override; std::unique_ptr CreateASTConsumer(clang::CompilerInstance &ci, llvm::StringRef) override; }; class MiniASTDumperConsumer : public clang::ASTConsumer , public clang::RecursiveASTVisitor { public: explicit MiniASTDumperConsumer(clang::CompilerInstance &ci); ~MiniASTDumperConsumer() override; bool VisitDecl(clang::Decl *decl); bool VisitStmt(clang::Stmt *stm); void HandleTranslationUnit(clang::ASTContext &ctx) override; private: MiniASTDumperConsumer(const MiniASTDumperConsumer &) = delete; void dumpCXXMethodDecl(clang::CXXMethodDecl *, CborEncoder *encoder); + void dumpFunctionDecl(clang::FunctionDecl *, CborEncoder *encoder); void dumpCXXRecordDecl(clang::CXXRecordDecl *, CborEncoder *encoder); void dumpCallExpr(clang::CallExpr *, CborEncoder *encoder); void dumpLocation(clang::SourceLocation, CborEncoder *encoder); void dumpFileMap(CborEncoder *encoder); void cborEncodeString(CborEncoder&, const char *); void cborEncodeInt(CborEncoder&, int64_t); void cborEncodeBool(CborEncoder &enc, bool); void cborCreateMap(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length); void cborCreateArray(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length); void cborCloseContainer(CborEncoder *encoder, const CborEncoder *containerEncoder); uint8_t *m_cborBuf = nullptr; size_t m_bufferSize = 1024 * 1024 * 20; // 20MB to start with CborEncoder m_cborEncoder, m_cborRootMapEncoder, m_cborStuffArray; clang::CompilerInstance &m_ci; std::unordered_map m_fileIds; std::string m_currentCppFile; }; #endif