Index: include/clang/AST/ASTContext.h =================================================================== --- include/clang/AST/ASTContext.h +++ include/clang/AST/ASTContext.h @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -78,12 +79,14 @@ } // end namespace llvm namespace clang { - +class ASTImporter; class ASTMutationListener; class ASTRecordLayout; +class ASTUnit; class AtomicExpr; class BlockExpr; class CharUnits; +class CompilerInstance; class CXXABI; class DiagnosticsEngine; class Expr; @@ -1902,6 +1905,24 @@ } //===--------------------------------------------------------------------===// + // Cross-translation unit support + //===--------------------------------------------------------------------===// +private: + llvm::StringMap> FileASTUnitMap; + llvm::StringMap FunctionAstUnitMap; + llvm::StringMap FunctionFileMap; + llvm::DenseMap> + ASTUnitImporterMap; + llvm::DenseMap ImportMap; + ASTImporter &getOrCreateASTImporter(ASTContext &From); + +public: + const FunctionDecl *getCTUDefinition( + const FunctionDecl *FD, CompilerInstance &CI, StringRef CTUDir, + DiagnosticsEngine &Diags, + std::function(StringRef)> Loader); + + //===--------------------------------------------------------------------===// // Type Sizing and Analysis //===--------------------------------------------------------------------===// Index: include/clang/AST/Mangle.h =================================================================== --- include/clang/AST/Mangle.h +++ include/clang/AST/Mangle.h @@ -52,6 +52,13 @@ ASTContext &Context; DiagnosticsEngine &Diags; const ManglerKind Kind; + // FIXME: Used for cross translation unit analysis. + // To reduce the risk of function name collision in C projects, we force + // name mangling for C functions when generating lookup identifiers for + // the static analyzer. + // We will no longer need this once link commands are also considered during + // analysis (or when we switching to use USRs instead of mangled names). + bool ShouldForceMangleProto; llvm::DenseMap GlobalBlockIds; llvm::DenseMap LocalBlockIds; @@ -87,6 +94,11 @@ return Result.first->second; } + bool shouldForceMangleProto() const { return ShouldForceMangleProto; } + void setShouldForceMangleProto(bool ForceMangleArguments) { + ShouldForceMangleProto = ForceMangleArguments; + } + /// @name Mangler Entry Points /// @{ Index: include/clang/StaticAnalyzer/Core/AnalyzerOptions.h =================================================================== --- include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -272,6 +272,9 @@ /// \sa shouldDisplayNotesAsEvents Optional DisplayNotesAsEvents; + /// \sa getCTUDir + Optional CTUDir; + /// A helper function that retrieves option for a given full-qualified /// checker name. /// Options for checkers can be specified via 'analyzer-config' command-line @@ -548,6 +551,9 @@ /// to false when unset. bool shouldDisplayNotesAsEvents(); + /// Returns the directory containing the CTU related files. + StringRef getCTUDir(); + public: AnalyzerOptions() : AnalysisStoreOpt(RegionStoreModel), Index: include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h =================================================================== --- include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -436,20 +436,7 @@ return cast(CallEvent::getDecl()); } - RuntimeDefinition getRuntimeDefinition() const override { - const FunctionDecl *FD = getDecl(); - // Note that the AnalysisDeclContext will have the FunctionDecl with - // the definition (if one exists). - if (FD) { - AnalysisDeclContext *AD = - getLocationContext()->getAnalysisDeclContext()-> - getManager()->getContext(FD); - if (AD->getBody()) - return RuntimeDefinition(AD->getDecl()); - } - - return RuntimeDefinition(); - } + RuntimeDefinition getRuntimeDefinition() const override; bool argumentsMayEscape() const override; Index: include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h =================================================================== --- include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -56,6 +56,8 @@ }; private: + CompilerInstance &CI; + AnalysisManager &AMgr; AnalysisDeclContextManager &AnalysisDeclContexts; @@ -97,9 +99,8 @@ InliningModes HowToInline; public: - ExprEngine(AnalysisManager &mgr, bool gcEnabled, - SetOfConstDecls *VisitedCalleesIn, - FunctionSummariesTy *FS, + ExprEngine(CompilerInstance &CI, AnalysisManager &mgr, bool gcEnabled, + SetOfConstDecls *VisitedCalleesIn, FunctionSummariesTy *FS, InliningModes HowToInlineIn); ~ExprEngine() override; @@ -132,6 +133,8 @@ BugReporter& getBugReporter() { return BR; } + CompilerInstance &getCompilerInstance() { return CI; } + const NodeBuilderContext &getBuilderContext() { assert(currBldrCtx); return *currBldrCtx; Index: lib/AST/ASTContext.cpp =================================================================== --- lib/AST/ASTContext.cpp +++ lib/AST/ASTContext.cpp @@ -13,6 +13,7 @@ #include "clang/AST/ASTContext.h" #include "CXXABI.h" +#include "clang/AST/ASTImporter.h" #include "clang/AST/ASTMutationListener.h" #include "clang/AST/Attr.h" #include "clang/AST/CharUnits.h" @@ -34,11 +35,16 @@ #include "clang/Basic/Builtins.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/Triple.h" #include "llvm/Support/Capacity.h" #include "llvm/Support/MathExtras.h" +#include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" +#include #include using namespace clang; @@ -1420,6 +1426,134 @@ } } + +//===----------------------------------------------------------------------===// +// Cross-translation unit support +//===----------------------------------------------------------------------===// + +std::string getMangledName(const NamedDecl *ND, MangleContext *MangleCtx) { + std::string MangledName; + llvm::raw_string_ostream OS(MangledName); + if (const auto *CCD = dyn_cast(ND)) + // FIXME: Use correct Ctor/DtorType + MangleCtx->mangleCXXCtor(CCD, Ctor_Complete, OS); + else if (const auto *CDD = dyn_cast(ND)) + MangleCtx->mangleCXXDtor(CDD, Dtor_Complete, OS); + else + MangleCtx->mangleName(ND, OS); + ASTContext &Ctx = ND->getASTContext(); + // We are not going to support vendor and don't support OS and environment. + // FIXME: support OS and environment correctly + llvm::Triple::ArchType T = Ctx.getTargetInfo().getTriple().getArch(); + if (T == llvm::Triple::thumb) + T = llvm::Triple::arm; + OS << "@" << Ctx.getTargetInfo().getTriple().getArchTypeName(T); + return OS.str(); +} + +/// Recursively visit the funtion decls of a DeclContext, and looks up a +/// function based on mangled name. +static const FunctionDecl * +findFunctionInDeclContext(const DeclContext *DC, StringRef MangledFnName, + std::unique_ptr &MangleCtx) { + if (!DC) + return nullptr; + for (const Decl *D : DC->decls()) { + const auto *SubDC = dyn_cast(D); + if (const auto *FD = + findFunctionInDeclContext(SubDC, MangledFnName, MangleCtx)) + return FD; + + const auto *ND = dyn_cast(D); + const FunctionDecl *ResultDecl; + if (!ND || !ND->hasBody(ResultDecl)) + continue; + std::string LookupMangledName = getMangledName(ResultDecl, MangleCtx.get()); + // We are already sure that the triple is correct here. + if (LookupMangledName != MangledFnName) + continue; + return ResultDecl; + } + return nullptr; +} + +const FunctionDecl *ASTContext::getCTUDefinition( + const FunctionDecl *FD, CompilerInstance &CI, StringRef CTUDir, + DiagnosticsEngine &Diags, + std::function(StringRef)> Loader) { + assert(!FD->hasBody() && "FD has a definition in current translation unit!"); + if (!FD->getType()->getAs()) + return nullptr; // Cannot even mangle that. + assert(ImportMap.find(FD) == ImportMap.end() && + "Functions already imported should have a body."); + + std::unique_ptr MangleCtx( + ItaniumMangleContext::create(FD->getASTContext(), Diags)); + MangleCtx->setShouldForceMangleProto(true); + std::string MangledFnName = getMangledName(FD, MangleCtx.get()); + ASTUnit *Unit = nullptr; + StringRef ASTFileName; + auto FnUnitCacheEntry = FunctionAstUnitMap.find(MangledFnName); + if (FnUnitCacheEntry == FunctionAstUnitMap.end()) { + if (FunctionFileMap.empty()) { + SmallString<128> ExternalFunctionMap = CTUDir; + llvm::sys::path::append(ExternalFunctionMap, "externalFnMap.txt"); + std::ifstream ExternalFnMapFile(ExternalFunctionMap.c_str()); + std::string FunctionName, FileName; + while (ExternalFnMapFile >> FunctionName >> FileName) { + SmallString<128> FilePath = CTUDir; + llvm::sys::path::append(FilePath, FileName); + FunctionFileMap[FunctionName] = FilePath.str().str(); + } + } + + auto It = FunctionFileMap.find(MangledFnName); + if (It != FunctionFileMap.end()) + ASTFileName = It->second; + else // No definition found even in some other build unit. + return nullptr; + auto ASTCacheEntry = FileASTUnitMap.find(ASTFileName); + if (ASTCacheEntry == FileASTUnitMap.end()) { + std::unique_ptr LoadedUnit(Loader(ASTFileName)); + Unit = LoadedUnit.get(); + FileASTUnitMap[ASTFileName] = std::move(LoadedUnit); + } else { + Unit = ASTCacheEntry->second.get(); + } + FunctionAstUnitMap[MangledFnName] = Unit; + } else { + Unit = FnUnitCacheEntry->second; + } + + if (!Unit) + return nullptr; + assert(&Unit->getFileManager() == + &Unit->getASTContext().getSourceManager().getFileManager()); + ASTImporter &Importer = getOrCreateASTImporter(Unit->getASTContext()); + TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl(); + if (const FunctionDecl *ResultDecl = + findFunctionInDeclContext(TU, MangledFnName, MangleCtx)) { + // FIXME: Refactor const_cast + auto *ToDecl = cast( + Importer.Import(const_cast(ResultDecl))); + assert(ToDecl->hasBody()); + ImportMap[FD] = ToDecl; + return ToDecl; + } + return nullptr; +} + +ASTImporter &ASTContext::getOrCreateASTImporter(ASTContext &From) { + auto I = ASTUnitImporterMap.find(From.getTranslationUnitDecl()); + if (I != ASTUnitImporterMap.end()) + return *I->second; + ASTImporter *NewImporter = new ASTImporter( + *this, getSourceManager().getFileManager(), + From, From.getSourceManager().getFileManager(), false); + ASTUnitImporterMap[From.getTranslationUnitDecl()].reset(NewImporter); + return *NewImporter; +} + CharUnits ASTContext::getDeclAlign(const Decl *D, bool ForAlignof) const { unsigned Align = Target->getCharWidth(); Index: lib/AST/ASTImporter.cpp =================================================================== --- lib/AST/ASTImporter.cpp +++ lib/AST/ASTImporter.cpp @@ -3206,6 +3206,8 @@ if (ToD) return ToD; + const FunctionDecl *FoundWithoutBody = nullptr; + // Try to find a function in our own ("to") context with the same name, same // type, and in the same context as the function we're importing. if (!LexicalDC->isFunctionOrMethod()) { @@ -3223,6 +3225,13 @@ if (Importer.IsStructurallyEquivalent(D->getType(), FoundFunction->getType())) { // FIXME: Actually try to merge the body and other attributes. + const FunctionDecl *FromBodyDecl = nullptr; + D->hasBody(FromBodyDecl); + if (D == FromBodyDecl && !FoundFunction->hasBody()) { + // This function is needed to merge completely. + FoundWithoutBody = FoundFunction; + break; + } return Importer.Imported(D, FoundFunction); } @@ -3373,6 +3382,12 @@ } ToFunction->setParams(Parameters); + if (FoundWithoutBody) { + auto *Recent = const_cast( + FoundWithoutBody->getMostRecentDecl()); + ToFunction->setPreviousDecl(Recent); + } + if (usedDifferentExceptionSpec) { // Update FunctionProtoType::ExtProtoInfo. QualType T = Importer.Import(D->getType()); Index: lib/AST/ItaniumMangle.cpp =================================================================== --- lib/AST/ItaniumMangle.cpp +++ lib/AST/ItaniumMangle.cpp @@ -79,6 +79,17 @@ if (FD->isExternC()) return FD->getASTContext().getTranslationUnitDecl(); + // Avoid infinite recursion with code like: + // void f(struct S* p); + // Where this is the first declaration of S. This is only valid for C. + // For some tools it makes sense to mangle C functions (e.g. avoid collisions + // when indexing). It might be nicer to check whether the Decl is in + // FunctionPrototypeScope, but this information is lost after the Sema is + // done. + if (!D->getASTContext().getLangOpts().CPlusPlus && DC->isFunctionOrMethod() && + isa(D)) + return D->getASTContext().getTranslationUnitDecl(); + return DC->getRedeclContext(); } @@ -653,7 +664,9 @@ // ::= // Don't mangle in the type if this isn't a decl we should typically mangle. - if (!Context.shouldMangleDeclName(FD)) { + if (!Context.shouldMangleDeclName(FD) && + !(Context.shouldForceMangleProto() && + FD->getType()->getAs())) { mangleName(FD); return; } Index: lib/Basic/SourceManager.cpp =================================================================== --- lib/Basic/SourceManager.cpp +++ lib/Basic/SourceManager.cpp @@ -2025,6 +2025,8 @@ } /// \brief Determines the order of 2 source locations in the translation unit. +/// FIXME: It also works when two locations are from different translation +/// units. In that case it will return *some* order. /// /// \returns true if LHS source location comes before RHS, false otherwise. bool SourceManager::isBeforeInTranslationUnit(SourceLocation LHS, @@ -2121,7 +2123,8 @@ return LIsScratch; return LOffs.second < ROffs.second; } - llvm_unreachable("Unsortable locations found"); + // FIXME: Source locations from different translation units. + return LOffs.first < ROffs.first; } void SourceManager::PrintStats() const { Index: lib/StaticAnalyzer/Core/AnalyzerOptions.cpp =================================================================== --- lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -370,3 +370,9 @@ getBooleanOption("notes-as-events", /*Default=*/false); return DisplayNotesAsEvents.getValue(); } + +StringRef AnalyzerOptions::getCTUDir() { + if (!CTUDir.hasValue() || !llvm::sys::fs::is_directory(*CTUDir)) + CTUDir = getOptionAsString("ctu-dir", ""); + return CTUDir.getValue(); +} Index: lib/StaticAnalyzer/Core/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Core/CMakeLists.txt +++ lib/StaticAnalyzer/Core/CMakeLists.txt @@ -56,6 +56,7 @@ clangAST clangAnalysis clangBasic + clangFrontend clangLex clangRewrite ${Z3_LINK_FILES} Index: lib/StaticAnalyzer/Core/CallEvent.cpp =================================================================== --- lib/StaticAnalyzer/Core/CallEvent.cpp +++ lib/StaticAnalyzer/Core/CallEvent.cpp @@ -16,6 +16,9 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/AST/ParentMap.h" #include "clang/Analysis/ProgramPoint.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" #include "llvm/ADT/SmallSet.h" @@ -355,6 +358,43 @@ D->parameters()); } +RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const { + const FunctionDecl *FD = getDecl(); + // Note that the AnalysisDeclContext will have the FunctionDecl with + // the definition (if one exists). + if (!FD) + return RuntimeDefinition(); + + AnalysisDeclContext *AD = + getLocationContext()->getAnalysisDeclContext()-> + getManager()->getContext(FD); + if (AD->getBody()) + return RuntimeDefinition(AD->getDecl()); + + auto Engine = static_cast( + getState()->getStateManager().getOwningEngine()); + CompilerInstance &CI = Engine->getCompilerInstance(); + + auto ASTLoader = [&](StringRef ASTFileName) { + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + TextDiagnosticPrinter *DiagClient = + new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts); + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + IntrusiveRefCntPtr Diags( + new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient)); + return ASTUnit::LoadFromASTFile( + ASTFileName, CI.getPCHContainerOperations()->getRawReader(), + Diags, CI.getFileSystemOpts()); + }; + + const FunctionDecl *CTUDecl = AD->getASTContext().getCTUDefinition( + FD, CI, Engine->getAnalysisManager().options.getCTUDir(), + CI.getDiagnostics(), ASTLoader); + + return RuntimeDefinition(CTUDecl); +} + + bool AnyFunctionCall::argumentsMayEscape() const { if (CallEvent::argumentsMayEscape() || hasVoidPointerToNonConstArg()) return true; Index: lib/StaticAnalyzer/Core/ExprEngine.cpp =================================================================== --- lib/StaticAnalyzer/Core/ExprEngine.cpp +++ lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -67,25 +67,19 @@ static const char* TagProviderName = "ExprEngine"; -ExprEngine::ExprEngine(AnalysisManager &mgr, bool gcEnabled, - SetOfConstDecls *VisitedCalleesIn, - FunctionSummariesTy *FS, - InliningModes HowToInlineIn) - : AMgr(mgr), - AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()), - Engine(*this, FS), - G(Engine.getGraph()), - StateMgr(getContext(), mgr.getStoreManagerCreator(), - mgr.getConstraintManagerCreator(), G.getAllocator(), - this), - SymMgr(StateMgr.getSymbolManager()), - svalBuilder(StateMgr.getSValBuilder()), - currStmtIdx(0), currBldrCtx(nullptr), - ObjCNoRet(mgr.getASTContext()), - ObjCGCEnabled(gcEnabled), BR(mgr, *this), - VisitedCallees(VisitedCalleesIn), - HowToInline(HowToInlineIn) -{ +ExprEngine::ExprEngine(CompilerInstance &CI, AnalysisManager &mgr, + bool gcEnabled, SetOfConstDecls *VisitedCalleesIn, + FunctionSummariesTy *FS, InliningModes HowToInlineIn) + : CI(CI), AMgr(mgr), + AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()), + Engine(*this, FS), G(Engine.getGraph()), + StateMgr(getContext(), mgr.getStoreManagerCreator(), + mgr.getConstraintManagerCreator(), G.getAllocator(), this), + SymMgr(StateMgr.getSymbolManager()), + svalBuilder(StateMgr.getSValBuilder()), currStmtIdx(0), + currBldrCtx(nullptr), ObjCNoRet(mgr.getASTContext()), + ObjCGCEnabled(gcEnabled), BR(mgr, *this), + VisitedCallees(VisitedCalleesIn), HowToInline(HowToInlineIn) { unsigned TrimInterval = mgr.options.getGraphTrimInterval(); if (TrimInterval != 0) { // Enable eager node reclaimation when constructing the ExplodedGraph. Index: lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp =================================================================== --- lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -16,13 +16,16 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/Mangle.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Analysis/Analyses/LiveVariables.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CallGraph.h" #include "clang/Analysis/CodeInjector.h" #include "clang/Basic/SourceManager.h" +#include "clang/Basic/TargetInfo.h" #include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Lex/Preprocessor.h" #include "clang/StaticAnalyzer/Checkers/LocalCheckers.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" @@ -40,9 +43,11 @@ #include "llvm/Support/Program.h" #include "llvm/Support/Timer.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include +#include using namespace clang; using namespace ento; @@ -168,6 +173,7 @@ AnalyzerOptionsRef Opts; ArrayRef Plugins; CodeInjector *Injector; + CompilerInstance &CI; /// \brief Stores the declarations from the local translation unit. /// Note, we pre-compute the local declarations at parse time as an @@ -192,12 +198,12 @@ /// translation unit. FunctionSummariesTy FunctionSummaries; - AnalysisConsumer(const Preprocessor &pp, const std::string &outdir, - AnalyzerOptionsRef opts, ArrayRef plugins, - CodeInjector *injector) + AnalysisConsumer(CompilerInstance &CI, const Preprocessor &pp, + const std::string &outdir, AnalyzerOptionsRef opts, + ArrayRef plugins, CodeInjector *injector) : RecVisitorMode(0), RecVisitorBR(nullptr), Ctx(nullptr), PP(pp), OutDir(outdir), Opts(std::move(opts)), Plugins(plugins), - Injector(injector) { + Injector(injector), CI(CI) { DigestAnalyzerOptions(); if (Opts->PrintStats) { llvm::EnableStatistics(false); @@ -415,6 +421,8 @@ } } +std::string getMangledName(const NamedDecl *ND, MangleContext *MangleCtx); + static bool shouldSkipFunction(const Decl *D, const SetOfConstDecls &Visited, const SetOfConstDecls &VisitedAsTopLevel) { @@ -706,7 +714,8 @@ if (!Mgr->getAnalysisDeclContext(D)->getAnalysis()) return; - ExprEngine Eng(*Mgr, ObjCGCEnabled, VisitedCallees, &FunctionSummaries,IMode); + ExprEngine Eng(CI, *Mgr, ObjCGCEnabled, VisitedCallees, &FunctionSummaries, + IMode); // Set the graph auditor. std::unique_ptr Auditor; @@ -764,7 +773,7 @@ bool hasModelPath = analyzerOpts->Config.count("model-path") > 0; return llvm::make_unique( - CI.getPreprocessor(), CI.getFrontendOpts().OutputFile, analyzerOpts, + CI, CI.getPreprocessor(), CI.getFrontendOpts().OutputFile, analyzerOpts, CI.getFrontendOpts().Plugins, hasModelPath ? new ModelInjector(CI) : nullptr); } Index: test/Analysis/Inputs/ctu-chain.cpp =================================================================== --- /dev/null +++ test/Analysis/Inputs/ctu-chain.cpp @@ -0,0 +1,20 @@ +int h_chain(int x) { + return x * 2; +} + +namespace chns { +int chf3(int x); + +int chf2(int x) { + return chf3(x); +} + +class chcls { +public: + int chf4(int x); +}; + +int chcls::chf4(int x) { + return x * 3; +} +} Index: test/Analysis/Inputs/ctu-other.cpp =================================================================== --- /dev/null +++ test/Analysis/Inputs/ctu-other.cpp @@ -0,0 +1,67 @@ +int callback_to_main(int x); +int f(int x) { + return x - 1; +} + +int g(int x) { + return callback_to_main(x) + 1; +} + +int h_chain(int); + +int h(int x) { + return 2 * h_chain(x); +} + +namespace myns { +int fns(int x) { + return x + 7; +} + +namespace embed_ns { +int fens(int x) { + return x - 3; +} +} + +class embed_cls { +public: + int fecl(int x) { + return x - 7; + } +}; +} + +class mycls { +public: + int fcl(int x) { + return x + 5; + } + static int fscl(int x) { + return x + 6; + } + + class embed_cls2 { + public: + int fecl2(int x) { + return x - 11; + } + }; +}; + +namespace chns { +int chf2(int x); + +class chcls { +public: + int chf4(int x); +}; + +int chf3(int x) { + return chcls().chf4(x); +} + +int chf1(int x) { + return chf2(x); +} +} Index: test/Analysis/Inputs/externalFnMap.txt =================================================================== --- /dev/null +++ test/Analysis/Inputs/externalFnMap.txt @@ -0,0 +1,14 @@ +_Z7h_chaini@x86_64 ctu-chain.cpp.ast +_ZN4chns4chf2Ei@x86_64 ctu-chain.cpp.ast +_ZN4chns5chcls4chf4Ei@x86_64 ctu-chain.cpp.ast +_Z1fi@x86_64 ctu-other.cpp.ast +_Z1gi@x86_64 ctu-other.cpp.ast +_Z1hi@x86_64 ctu-other.cpp.ast +_ZN4myns9embed_cls4feclEi@x86_64 ctu-other.cpp.ast +_ZN4myns3fnsEi@x86_64 ctu-other.cpp.ast +_ZN5mycls10embed_cls25fecl2Ei@x86_64 ctu-other.cpp.ast +_ZN5mycls4fsclEi@x86_64 ctu-other.cpp.ast +_ZN4chns4chf3Ei@x86_64 ctu-other.cpp.ast +_ZN5mycls3fclEi@x86_64 ctu-other.cpp.ast +_ZN4chns4chf1Ei@x86_64 ctu-other.cpp.ast +_ZN4myns8embed_ns4fensEi@x86_64 ctu-other.cpp.ast Index: test/Analysis/ctu-main.cpp =================================================================== --- /dev/null +++ test/Analysis/ctu-main.cpp @@ -0,0 +1,58 @@ +// RUN: mkdir -p %T/ctudir +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -emit-pch -o %T/ctudir/ctu-other.cpp.ast %S/Inputs/ctu-other.cpp +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -emit-pch -o %T/ctudir/ctu-chain.cpp.ast %S/Inputs/ctu-chain.cpp +// RUN: cp %S/Inputs/externalFnMap.txt %T/ctudir/ +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fsyntax-only -analyze -analyzer-checker=core,debug.ExprInspection -analyzer-config ctu-dir=%T/ctudir -analyzer-config reanalyze-ctu-visited=true -verify %s + +void clang_analyzer_eval(int); + +int f(int); +int g(int); +int h(int); + +int callback_to_main(int x) { return x + 1; } + +namespace myns { +int fns(int x); + +namespace embed_ns { +int fens(int x); +} + +class embed_cls { +public: + int fecl(int x); +}; +} + +class mycls { +public: + int fcl(int x); + static int fscl(int x); + + class embed_cls2 { + public: + int fecl2(int x); + }; +}; + +namespace chns { +int chf1(int x); +} + +int main() { + clang_analyzer_eval(f(3) == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(f(4) == 3); // expected-warning{{TRUE}} + clang_analyzer_eval(f(5) == 3); // expected-warning{{FALSE}} + clang_analyzer_eval(g(4) == 6); // expected-warning{{TRUE}} + clang_analyzer_eval(h(2) == 8); // expected-warning{{TRUE}} + + clang_analyzer_eval(myns::fns(2) == 9); // expected-warning{{TRUE}} + clang_analyzer_eval(myns::embed_ns::fens(2) == -1); // expected-warning{{TRUE}} + clang_analyzer_eval(mycls().fcl(1) == 6); // expected-warning{{TRUE}} + clang_analyzer_eval(mycls::fscl(1) == 7); // expected-warning{{TRUE}} + clang_analyzer_eval(myns::embed_cls().fecl(1) == -6); // expected-warning{{TRUE}} + clang_analyzer_eval(mycls::embed_cls2().fecl2(0) == -11); // expected-warning{{TRUE}} + + clang_analyzer_eval(chns::chf1(4) == 12); // expected-warning{{TRUE}} +} Index: test/Analysis/func-mapping-test.cpp =================================================================== --- /dev/null +++ test/Analysis/func-mapping-test.cpp @@ -0,0 +1,7 @@ +// RUN: %clang_func_map %s -- -target x86_64-unknown-linux-gnu | FileCheck %s + +int f(int) { + return 0; +} + +// CHECK: _Z1fi Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -47,6 +47,7 @@ clang-tblgen clang-offload-bundler clang-import-test + clang-func-mapping ) if(CLANG_ENABLE_STATIC_ANALYZER) Index: test/lit.cfg =================================================================== --- test/lit.cfg +++ test/lit.cfg @@ -271,6 +271,7 @@ ' --driver-mode=cl ')) config.substitutions.append( ('%clangxx', ' ' + config.clang + ' --driver-mode=g++ ')) +config.substitutions.append( ('%clang_func_map', ' ' + lit.util.which('clang-func-mapping', config.environment['PATH']) + ' ') ) config.substitutions.append( ('%clang', ' ' + config.clang + ' ') ) config.substitutions.append( ('%test_debuginfo', ' ' + config.llvm_src_root + '/utils/test_debuginfo.pl ') ) config.substitutions.append( ('%itanium_abi_triple', makeItaniumABITriple(config.target_triple)) ) Index: tools/CMakeLists.txt =================================================================== --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -19,6 +19,8 @@ add_clang_subdirectory(clang-check) add_clang_subdirectory(scan-build) add_clang_subdirectory(scan-view) + add_clang_subdirectory(clang-func-mapping) + add_clang_subdirectory(clang-cmdline-arch-extractor) endif() # We support checking out the clang-tools-extra repository into the 'extra' Index: tools/clang-func-mapping/CMakeLists.txt =================================================================== --- /dev/null +++ tools/clang-func-mapping/CMakeLists.txt @@ -0,0 +1,21 @@ +set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + asmparser + support + mc + ) + +add_clang_executable(clang-func-mapping + ClangFnMapGen.cpp + ) + +target_link_libraries(clang-func-mapping + clangTooling + clangAST + clangBasic + clangFrontend + clangRewriteFrontend + ) + +install(TARGETS clang-func-mapping + RUNTIME DESTINATION bin) Index: tools/clang-func-mapping/ClangFnMapGen.cpp =================================================================== --- /dev/null +++ tools/clang-func-mapping/ClangFnMapGen.cpp @@ -0,0 +1,144 @@ +//===- ClangFnMapGen.cpp -----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===--------------------------------------------------------------------===// +// +// Clang tool which creates a list of defined functions and the files in which +// they are defined. +// +//===--------------------------------------------------------------------===// + +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/Mangle.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Signals.h" +#include +#include +#include + +using namespace llvm; +using namespace clang; +using namespace clang::tooling; + +static cl::OptionCategory ClangFnMapGenCategory("clang-fnmapgen options"); + +class MapFunctionNamesConsumer : public ASTConsumer { +private: + ASTContext &Ctx; + ItaniumMangleContext *ItaniumCtx; + std::stringstream DefinedFuncsStr; + +public: + MapFunctionNamesConsumer(ASTContext &Context, ItaniumMangleContext *MangleCtx) + : Ctx(Context), ItaniumCtx(MangleCtx) {} + std::string CurrentFileName; + + ~MapFunctionNamesConsumer(); + virtual void HandleTranslationUnit(ASTContext &Ctx) { + handleDecl(Ctx.getTranslationUnitDecl()); + } + +private: + std::string getMangledName(const FunctionDecl *FD); + void handleDecl(const Decl *D); +}; + +std::string MapFunctionNamesConsumer::getMangledName(const FunctionDecl *FD) { + std::string MangledName; + llvm::raw_string_ostream os(MangledName); + if (const auto *CCD = dyn_cast(FD)) + // FIXME: Use correct Ctor/DtorType. + ItaniumCtx->mangleCXXCtor(CCD, Ctor_Complete, os); + else if (const auto *CDD = dyn_cast(FD)) + ItaniumCtx->mangleCXXDtor(CDD, Dtor_Complete, os); + else + ItaniumCtx->mangleName(FD, os); + os.flush(); + return MangledName; +} + +void MapFunctionNamesConsumer::handleDecl(const Decl *D) { + if (!D) + return; + + if (const auto *FD = dyn_cast(D)) { + if (FD->isThisDeclarationADefinition()) { + if (const Stmt *Body = FD->getBody()) { + std::string MangledName = getMangledName(FD); + const SourceManager &SM = Ctx.getSourceManager(); + if (CurrentFileName.empty()) { + StringRef SMgrName = + SM.getFileEntryForID(SM.getMainFileID())->getName(); + char *Path = realpath(SMgrName.str().c_str(), nullptr); + CurrentFileName = Path; + free(Path); + } + + switch (FD->getLinkageInternal()) { + case ExternalLinkage: + case VisibleNoLinkage: + case UniqueExternalLinkage: + if (SM.isInMainFile(Body->getLocStart())) + DefinedFuncsStr << MangledName << " " << CurrentFileName << "\n"; + default: + break; + } + } + } + } + + if (const auto *DC = dyn_cast(D)) + for (const Decl *D : DC->decls()) + handleDecl(D); +} + +MapFunctionNamesConsumer::~MapFunctionNamesConsumer() { + // Flush results to standard output. + llvm::outs() << DefinedFuncsStr.str(); +} + +class MapFunctionNamesAction : public ASTFrontendAction { +protected: + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + llvm::StringRef) { + ItaniumMangleContext *ItaniumCtx = + ItaniumMangleContext::create(CI.getASTContext(), CI.getDiagnostics()); + ItaniumCtx->setShouldForceMangleProto(true); + std::unique_ptr PFC( + new MapFunctionNamesConsumer(CI.getASTContext(), ItaniumCtx)); + return PFC; + } +}; + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); + +int main(int argc, const char **argv) { + // Print a stack trace if we signal out. + sys::PrintStackTraceOnErrorSignal(argv[0], false); + PrettyStackTraceProgram X(argc, argv); + + const char *Overview = "\nThis tool collects the mangled name and location " + "of all functions definitions in the source files " + "(excluding headers).\n"; + CommonOptionsParser OptionsParser(argc, argv, ClangFnMapGenCategory, + cl::ZeroOrMore, Overview); + + ClangTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + Tool.run(newFrontendActionFactory().get()); + return 0; +} Index: tools/ctu-analysis/ctu-analyze.py =================================================================== --- /dev/null +++ tools/ctu-analysis/ctu-analyze.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python + +import argparse +import json +import logging +import multiprocessing +import os +import re +import signal +import subprocess +import threading +import time + + +SOURCE_PATTERN = re.compile('.*\.(C|c|cc|cpp|cxx|ii|m|mm)$', re.IGNORECASE) +DIRCMD_SEPARATOR = ': ' + + +def get_args(): + analyser_output_formats = ['plist-multi-file', 'plist', 'plist-html', + 'html', 'text'] + analyser_output_format = analyser_output_formats[0] + parser = argparse.ArgumentParser( + description='Executes 2nd pass of CTU analysis where we do the ' + 'static analysis taking all cross calls between ' + 'translation units into account', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-b', required=True, dest='buildlog', + metavar='build.json', + help='JSON Compilation Database to be used') + parser.add_argument('-p', metavar='preanalyze-dir', dest='ctuindir', + help='Use directory for reading preanalyzation data ', + default='.ctu') + parser.add_argument('-o', metavar='output-dir', dest='ctuoutdir', + help='Target directory for analyzation results', + default='.ctu-out') + parser.add_argument('-e', metavar='enabled-checker', nargs='+', + dest='enabled_checkers', + help='List all enabled checkers') + parser.add_argument('-d', metavar='disabled-checker', nargs='+', + dest='disabled_checkers', + help='List all disabled checkers') + parser.add_argument('-j', metavar='threads', dest='threads', type=int, + help='Number of threads to be used', + default=int(multiprocessing.cpu_count() * 1.0)) + parser.add_argument('-v', dest='verbose', action='store_true', + help='Verbose output') + parser.add_argument('--clang-path', metavar='clang-path', + dest='clang_path', + help='Set path to directory of clang binaries used ' + '(default taken from CLANG_PATH envvar)', + default=os.environ.get('CLANG_PATH')) + parser.add_argument('--analyze-cc-path', metavar='analyze-cc-path', + dest='analyze_path', + help='Set path to directory of analyze-cc used ' + '(default is taken from CLANG_ANALYZE_CC_PATH ' + 'environment variable)', + default=os.environ.get('CLANG_ANALYZE_CC_PATH')) + parser.add_argument('--output-format', metavar='format', + choices=analyser_output_formats, + default=analyser_output_format, + help='Format for analysis reports ' + '(one of %s; default is "%s").' % + (', '.join(analyser_output_formats), + analyser_output_format)) + parser.add_argument('--no-ctu', dest='no_ctu', action='store_true', + help='Do not use CTU at all, ' + 'only do normal static analysis') + mainargs = parser.parse_args() + + if mainargs.verbose: + logging.getLogger().setLevel(logging.INFO) + + mainargs.ctuindir = os.path.abspath(mainargs.ctuindir) + mainargs.ctuoutdir = os.path.abspath(mainargs.ctuoutdir) + + if mainargs.clang_path is None: + clang_path = '' + else: + clang_path = os.path.abspath(mainargs.clang_path) + logging.info('CTU uses clang dir: ' + + (clang_path if clang_path != '' else '')) + + if mainargs.analyze_path is None: + analyze_path = '' + else: + analyze_path = os.path.abspath(mainargs.analyze_path) + logging.info('CTU uses analyze-cc dir: ' + + (analyze_path if analyze_path != '' else '')) + + return mainargs, clang_path, analyze_path + + +def get_analyzer_env(mainargs, clang_path): + analyzer_params = [] + if mainargs.enabled_checkers: + analyzer_params.append('-analyzer-checker') + analyzer_params.append(mainargs.enabled_checkers) + if mainargs.disabled_checkers: + analyzer_params.append('-analyzer-disable-checker') + analyzer_params.append(mainargs.disabled_checkers) + if not mainargs.no_ctu: + analyzer_params.append('-analyzer-config') + analyzer_params.append('ctu-dir=' + mainargs.ctuindir) + analyzer_params.append('-analyzer-config') + analyzer_params.append('reanalyze-ctu-visited=true') + analyzer_params.append('-analyzer-stats') + passthru_analyzer_params = [] + for param in analyzer_params: + passthru_analyzer_params.append('-Xanalyzer') + passthru_analyzer_params.append(param) + passthru_analyzer_params.append('--analyzer-output') + passthru_analyzer_params.append(mainargs.output_format) + analyzer_env = os.environ.copy() + analyzer_env['ANALYZE_BUILD_CLANG'] = os.path.join(clang_path, 'clang') + analyzer_env['ANALYZE_BUILD_REPORT_DIR'] = mainargs.ctuoutdir + analyzer_env['ANALYZE_BUILD_REPORT_FORMAT'] = mainargs.output_format + analyzer_env['ANALYZE_BUILD_REPORT_FAILURES'] = 'yes' + analyzer_env['ANALYZE_BUILD_PARAMETERS'] = \ + ' '.join(passthru_analyzer_params) + return analyzer_env + + +def process_buildlog(buildlog_filename): + with open(buildlog_filename, 'r') as buildlog_file: + buildlog = json.load(buildlog_file) + dircmd_2_orders = {} + src_build_steps = 0 + for step in buildlog: + if SOURCE_PATTERN.match(step['file']): + uid = step['directory'] + DIRCMD_SEPARATOR + step['command'] + if uid not in dircmd_2_orders: + dircmd_2_orders[uid] = [src_build_steps] + else: + dircmd_2_orders[uid].append(src_build_steps) + src_build_steps += 1 + return dircmd_2_orders + + +def prepare_workspace(mainargs): + try: + os.makedirs(mainargs.ctuoutdir) + except OSError: + logging.warning('Output directory %s already exists!', + mainargs.ctuoutdir) + exit(1) + + +def clean_up_workspace(mainargs): + try: + os.removedirs(mainargs.ctuoutdir) + logging.info('Removing directory %s because it contains no reports', + mainargs.ctuoutdir) + except OSError: + pass + + +def get_compiler_and_arguments(cmd): + had_command = False + args = [] + for arg in cmd.split(): + if had_command: + args.append(arg) + if not had_command and arg.find('=') == -1: + had_command = True + compiler = arg + return compiler, args + + +def analyze(mainargs, analyze_path, analyzer_env, directory, command): + compiler, args = get_compiler_and_arguments(command) + cmdenv = analyzer_env.copy() + cmdenv['INTERCEPT_BUILD'] = json.dumps({ + 'verbose': 1 if mainargs.verbose else 0, + 'cc': [compiler], + 'cxx': [compiler] + }) + analyze_cmd_str = os.path.join(analyze_path, 'analyze-cc') + ' ' + \ + ' '.join(args) + logging.info(analyze_cmd_str) + + # Buffer output of subprocess and dump it out at the end, so that + # the subprocess doesn't continue to write output after the user + # sends SIGTERM + run_ok = True + out = '******* Error running command' + try: + popen = subprocess.Popen(analyze_cmd_str, shell=True, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + universal_newlines=True, + cwd=directory, + env=cmdenv) + out, _ = popen.communicate() + run_ok = not popen.returncode + except OSError: + run_ok = False + logging.info('Compile status: %s', 'ok' if run_ok else 'failed') + logging.info(out) + + +def analyze_worker(mainargs, analyze_path, analyzer_env, lock, + dircmd_2_orders): + while len(dircmd_2_orders) > 0: + lock.acquire() + if len(dircmd_2_orders) > 0: + dircmd, orders = dircmd_2_orders.popitem() + lock.release() + dircmdsplit = dircmd.split(DIRCMD_SEPARATOR, 2) + assert len(dircmdsplit) == 2 and len(dircmdsplit[0]) > 0 and \ + len(dircmdsplit[1]) > 0 + assert len(orders) > 0 + directory = dircmdsplit[0] + command = dircmdsplit[1] + analyze(mainargs, analyze_path, analyzer_env, directory, command) + else: + lock.release() + time.sleep(0.125) + + +def run_parallel(mainargs, analyze_path, analyzer_env, dircmd_2_orders): + original_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGINT, original_handler) + lock = threading.Lock() + workers = [] + for _ in range(mainargs.threads): + workers.append(threading.Thread(target=analyze_worker, + args=(mainargs, analyze_path, + analyzer_env, lock, + dircmd_2_orders))) + for worker in workers: + worker.start() + try: + for worker in workers: + worker.join(9999999999) + except KeyboardInterrupt: + exit(1) + + +def main(): + mainargs, clang_path, analyze_path = get_args() + analyzer_env = get_analyzer_env(mainargs, clang_path) + dircmd_2_orders = process_buildlog(mainargs.buildlog) + prepare_workspace(mainargs) + run_parallel(mainargs, analyze_path, analyzer_env, dircmd_2_orders) + clean_up_workspace(mainargs) + + +if __name__ == "__main__": + main() Index: tools/ctu-analysis/ctu-build.py =================================================================== --- /dev/null +++ tools/ctu-analysis/ctu-build.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python + +import argparse +import json +import logging +import multiprocessing +import os +import re +import signal +import subprocess +import shlex + +SOURCE_PATTERN = re.compile('.*\.(C|c|cc|cpp|cxx|ii|m|mm)$', re.IGNORECASE) +TIMEOUT = 86400 +DEFINED_FUNCTIONS_FILENAME = 'definedFns.txt' +EXTERNAL_FUNCTIONS_FILENAME = 'externalFns.txt' +EXTERNAL_FUNCTION_MAP_FILENAME = 'externalFnMap.txt' + + +def get_args(): + parser = argparse.ArgumentParser( + description='Executes 1st pass of CTU analysis where we preprocess ' + 'all files in the compilation database and generate ' + 'AST dumps and other necessary information from those ' + 'to be used later by the 2nd pass of ' + 'Cross Translation Unit analysis', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-b', required=True, dest='buildlog', + metavar='build.json', + help='JSON Compilation Database to be used') + parser.add_argument('-p', metavar='preanalyze-dir', dest='ctuindir', + help='Target directory for preanalyzation data', + default='.ctu') + parser.add_argument('-j', metavar='threads', dest='threads', type=int, + help='Number of threads to be used', + default=int(multiprocessing.cpu_count() * 1.0)) + parser.add_argument('-v', dest='verbose', action='store_true', + help='Verbose output') + parser.add_argument('--clang-path', metavar='clang-path', + dest='clang_path', + help='Set path to directory of clang binaries used ' + '(default taken from CLANG_PATH envvar)', + default=os.environ.get('CLANG_PATH')) + mainargs = parser.parse_args() + + if mainargs.verbose: + logging.getLogger().setLevel(logging.INFO) + + if mainargs.clang_path is None: + clang_path = '' + else: + clang_path = os.path.abspath(mainargs.clang_path) + logging.info('CTU uses clang dir: ' + + (clang_path if clang_path != '' else '')) + + return mainargs, clang_path + + +def process_buildlog(buildlog_filename, src_2_dir, src_2_cmd, src_order, + cmd_2_src, cmd_order): + with open(buildlog_filename, 'r') as buildlog_file: + buildlog = json.load(buildlog_file) + for step in buildlog: + if SOURCE_PATTERN.match(step['file']): + if step['file'] not in src_2_dir: + src_2_dir[step['file']] = step['directory'] + src_2_cmd[step['file']] = step['command'] + src_order.append(step['file']) + if step['command'] not in cmd_2_src: + cmd_2_src[step['command']] = [step['file']] + cmd_order.append(step['command']) + else: + cmd_2_src[step['command']].append(step['file']) + + +def clear_file(filename): + try: + os.remove(filename) + except OSError: + pass + + +def clear_workspace(ctuindir): + clear_file(os.path.join(ctuindir, DEFINED_FUNCTIONS_FILENAME)) + clear_file(os.path.join(ctuindir, EXTERNAL_FUNCTIONS_FILENAME)) + clear_file(os.path.join(ctuindir, EXTERNAL_FUNCTION_MAP_FILENAME)) + + +def get_command_arguments(cmd): + had_command = False + args = [] + for arg in cmd.split(): + if had_command and not SOURCE_PATTERN.match(arg): + args.append(arg) + if not had_command and arg.find('=') == -1: + had_command = True + return args + + +def get_triple_arch(clang_path, clang_args, source): + """Returns the architecture part of the target triple in a compilation + command. """ + arch = "" + clang_cmd = [os.path.join(clang_path, 'clang'), "-###"] + clang_cmd.extend(clang_args) + clang_cmd.append(source) + clang_out = subprocess.check_output(clang_cmd, stderr=subprocess.STDOUT, + shell=False) + clang_params = shlex.split(clang_out) + i = 0 + while i < len(clang_params) and clang_params[i] != "-triple": + i += 1 + if i < (len(clang_params) - 1): + arch = clang_params[i + 1].split("-")[0] + return arch + + +def generate_ast(params): + source, command, directory, clang_path, ctuindir = params + args = get_command_arguments(command) + arch = get_triple_arch(clang_path, args, source) + ast_joined_path = os.path.join(ctuindir, 'ast', arch, + os.path.realpath(source)[1:] + '.ast') + ast_path = os.path.abspath(ast_joined_path) + try: + os.makedirs(os.path.dirname(ast_path)) + except OSError: + if os.path.isdir(os.path.dirname(ast_path)): + pass + else: + raise + dir_command = ['cd', directory] + ast_command = [os.path.join(clang_path, 'clang'), '-emit-ast'] + ast_command.extend(args) + ast_command.append('-w') + ast_command.append(source) + ast_command.append('-o') + ast_command.append(ast_path) + ast_command_str = ' '.join(dir_command) + " && " + ' '.join(ast_command) + logging.info(ast_command_str) + subprocess.call(ast_command_str, shell=True) + + +def map_functions(params): + command, sources, directory, clang_path, ctuindir = params + args = get_command_arguments(command) + logging.info("map_funcions command " + command) + logging.info("sources: " + sources) + arch = get_triple_arch(clang_path, args, sources[0]) + dir_command = ['cd', directory] + funcmap_command = [os.path.join(clang_path, 'clang-func-mapping')] + funcmap_command.extend(sources) + funcmap_command.append('--') + funcmap_command.extend(args) + funcmap_command_str = ' '.join(dir_command) + \ + " && " + ' '.join(funcmap_command) + logging.info(funcmap_command_str) + logging.info("Calling function map:" + funcmap_command_str) + output = [] + fnOut = subprocess.check_output(funcmap_command_str, shell=True) + fnList = fnOut.splitlines() + for fnTxt in fnList: + d = fnTxt.find(" ") + mangledName = fnTxt[0:d] + path = fnTxt[d + 1:] + astPath = os.path.join("ast", arch, path[1:] + ".ast") + output.append(mangledName + "@" + arch + " " + astPath) + extern_fns_map_filename = os.path.join(ctuindir, + EXTERNAL_FUNCTION_MAP_FILENAME) + logging.info("functionmap: " + output) + with open(extern_fns_map_filename, 'a') as out_file: + out_file.write("\n".join(output) + "\n") + + +def run_parallel(threads, workfunc, funcparams): + original_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) + workers = multiprocessing.Pool(processes=threads) + signal.signal(signal.SIGINT, original_handler) + try: + # Block with timeout so that signals don't get ignored, python bug 8296 + workers.map_async(workfunc, funcparams).get(TIMEOUT) + except KeyboardInterrupt: + workers.terminate() + workers.join() + exit(1) + else: + workers.close() + workers.join() + + +def main(): + mainargs, clang_path = get_args() + clear_workspace(mainargs.ctuindir) + + src_2_dir = {} + src_2_cmd = {} + src_order = [] + cmd_2_src = {} + cmd_order = [] + process_buildlog(mainargs.buildlog, src_2_dir, src_2_cmd, src_order, + cmd_2_src, cmd_order) + + run_parallel(mainargs.threads, generate_ast, + [(src, src_2_cmd[src], src_2_dir[src], clang_path, + mainargs.ctuindir) for src in src_order]) + + # Run on one thread so no locking needed. + run_parallel(1, map_functions, + [(cmd, cmd_2_src[cmd], src_2_dir[cmd_2_src[cmd][0]], + clang_path, mainargs.ctuindir) for cmd in cmd_order]) + + +if __name__ == "__main__": + main() Index: tools/scan-build-py/libscanbuild/runner.py =================================================================== --- tools/scan-build-py/libscanbuild/runner.py +++ tools/scan-build-py/libscanbuild/runner.py @@ -162,7 +162,8 @@ def target(): """ Creates output file name for reports. """ - if opts['output_format'] in {'plist', 'plist-html'}: + if opts['output_format'] in {'plist', 'plist-html', + 'plist-multi-file'}: (handle, name) = tempfile.mkstemp(prefix='report-', suffix='.plist', dir=opts['output_dir'])