Index: include/clang/Analysis/AnalysisContext.h =================================================================== --- include/clang/Analysis/AnalysisContext.h +++ include/clang/Analysis/AnalysisContext.h @@ -152,6 +152,10 @@ /// \sa getBody bool isBodyAutosynthesizedFromModelFile() const; + /// \brief Retrieve the declaration from the model file. + /// \sa getModelDecl + FunctionDecl *getModelDecl() const; + CFG *getCFG(); CFGStmtMap *getCFGStmtMap(); @@ -416,12 +420,16 @@ /// for well-known functions. bool SynthesizeBodies; + /// Flag to indicate whether or not model attributes should be merged. + bool MergeModelAttrs; + public: AnalysisDeclContextManager(bool useUnoptimizedCFG = false, bool addImplicitDtors = false, bool addInitializers = false, bool addTemporaryDtors = false, bool synthesizeBodies = false, + bool mergeModelAttrs = false, bool addStaticInitBranches = false, bool addCXXNewAllocator = true, CodeInjector* injector = nullptr); @@ -442,6 +450,10 @@ /// functions. bool synthesizeBodies() const { return SynthesizeBodies; } + /// Return true if attributes of functions' declarations taken from model + /// files should be merged with existing ones. + bool mergeModelAttributes() const { return MergeModelAttrs; } + const StackFrameContext *getStackFrame(AnalysisDeclContext *Ctx, LocationContext const *Parent, const Stmt *S, Index: include/clang/Analysis/CodeInjector.h =================================================================== --- include/clang/Analysis/CodeInjector.h +++ include/clang/Analysis/CodeInjector.h @@ -40,6 +40,8 @@ virtual Stmt *getBody(const FunctionDecl *D) = 0; virtual Stmt *getBody(const ObjCMethodDecl *D) = 0; + /// \brief Returns the model's declaration. + virtual FunctionDecl *getModelDecl(const FunctionDecl *D) = 0; }; } Index: include/clang/StaticAnalyzer/Core/AnalyzerOptions.h =================================================================== --- include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -496,6 +496,10 @@ /// for well-known functions. bool shouldSynthesizeBodies(); + /// Return true if attributes of functions' declarations taken from model + /// files should be merged with existing ones. + bool shouldMergeModelAttributes(); + /// Returns how often nodes in the ExplodedGraph should be recycled to save /// memory. /// Index: include/clang/StaticAnalyzer/Frontend/FrontendActions.h =================================================================== --- include/clang/StaticAnalyzer/Frontend/FrontendActions.h +++ include/clang/StaticAnalyzer/Frontend/FrontendActions.h @@ -40,7 +40,8 @@ /// parsed, the function definitions will be collected into a StringMap. class ParseModelFileAction : public ASTFrontendAction { public: - ParseModelFileAction(llvm::StringMap &Bodies); + ParseModelFileAction(llvm::StringMap &Bodies, + llvm::StringMap &Decls); bool isModelParsingAction() const override { return true; } protected: @@ -49,6 +50,8 @@ private: llvm::StringMap &Bodies; + /// Stores the models' declarations. + llvm::StringMap &Decls; }; void printCheckerHelp(raw_ostream &OS, ArrayRef plugins); Index: include/clang/StaticAnalyzer/Frontend/ModelConsumer.h =================================================================== --- include/clang/StaticAnalyzer/Frontend/ModelConsumer.h +++ include/clang/StaticAnalyzer/Frontend/ModelConsumer.h @@ -31,12 +31,15 @@ /// from a model file. class ModelConsumer : public ASTConsumer { public: - ModelConsumer(llvm::StringMap &Bodies); + ModelConsumer(llvm::StringMap &Bodies, + llvm::StringMap &Decls); bool HandleTopLevelDecl(DeclGroupRef D) override; private: llvm::StringMap &Bodies; + /// Store the models' declarations. + llvm::StringMap &Decls; }; } } Index: lib/Analysis/AnalysisDeclContext.cpp =================================================================== --- lib/Analysis/AnalysisDeclContext.cpp +++ lib/Analysis/AnalysisDeclContext.cpp @@ -68,10 +68,13 @@ bool addInitializers, bool addTemporaryDtors, bool synthesizeBodies, + bool mergeModelAttrs, bool addStaticInitBranch, bool addCXXNewAllocator, CodeInjector *injector) - : Injector(injector), SynthesizeBodies(synthesizeBodies) + : Injector(injector), + SynthesizeBodies(synthesizeBodies), + MergeModelAttrs(mergeModelAttrs) { cfgBuildOptions.PruneTriviallyFalseEdges = !useUnoptimizedCFG; cfgBuildOptions.AddImplicitDtors = addImplicitDtors; @@ -135,6 +138,15 @@ return Tmp && Body->getLocStart().isValid(); } +FunctionDecl *AnalysisDeclContext::getModelDecl() const { + if (const FunctionDecl *FD = dyn_cast(D)) { + if (Manager) { + return getBodyFarm(getASTContext(), Manager->Injector.get()) + .getModelDecl(FD); + } + } + return nullptr; +} const ImplicitParamDecl *AnalysisDeclContext::getSelfDecl() const { if (const ObjCMethodDecl *MD = dyn_cast(D)) Index: lib/Analysis/BodyFarm.h =================================================================== --- lib/Analysis/BodyFarm.h +++ lib/Analysis/BodyFarm.h @@ -39,11 +39,16 @@ /// Factory method for creating bodies for Objective-C properties. Stmt *getBody(const ObjCMethodDecl *D); + /// \brief Retrieve the declaration from the model file. + FunctionDecl *getModelDecl(const FunctionDecl *FD); + private: typedef llvm::DenseMap > BodyMap; + typedef llvm::DenseMap> DeclsMap; ASTContext &C; BodyMap Bodies; + DeclsMap Decls; CodeInjector *Injector; }; } Index: lib/Analysis/BodyFarm.cpp =================================================================== --- lib/Analysis/BodyFarm.cpp +++ lib/Analysis/BodyFarm.cpp @@ -386,6 +386,26 @@ return Val.getValue(); } +FunctionDecl *BodyFarm::getModelDecl(const FunctionDecl *D) { + D = D->getCanonicalDecl(); + + Optional &Val = Decls[D]; + if (Val.hasValue()) + return Val.getValue(); + + Val = nullptr; + + if (D->getIdentifier() == nullptr) + return nullptr; + + StringRef Name = D->getName(); + if (Name.empty()) + return nullptr; + + if (Injector) {Val = Injector->getModelDecl(D); } + return Val.getValue(); +} + static Stmt *createObjCPropertyGetter(ASTContext &Ctx, const ObjCPropertyDecl *Prop) { // First, find the backing ivar. Index: lib/StaticAnalyzer/Core/AnalysisManager.cpp =================================================================== --- lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -27,6 +27,7 @@ /*AddInitializers=*/true, Options.includeTemporaryDtorsInCFG(), Options.shouldSynthesizeBodies(), + Options.shouldMergeModelAttributes(), Options.shouldConditionalizeStaticInitializers(), /*addCXXNewAllocator=*/true, injector), Index: lib/StaticAnalyzer/Core/AnalyzerOptions.cpp =================================================================== --- lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -325,6 +325,10 @@ return getBooleanOption("faux-bodies", true); } +bool AnalyzerOptions::shouldMergeModelAttributes() { + return getBooleanOption("faux-attributes", false); +} + bool AnalyzerOptions::shouldPrunePaths() { return getBooleanOption("prune-paths", true); } Index: lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp =================================================================== --- lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -27,6 +27,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/Preprocessor.h" +#include "clang/Sema/Sema.h" #include "clang/StaticAnalyzer/Checkers/LocalCheckers.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" @@ -161,6 +162,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 @@ -189,9 +191,10 @@ const std::string& outdir, AnalyzerOptionsRef opts, ArrayRef plugins, - CodeInjector *injector) + CodeInjector *injector, + CompilerInstance &CI) : RecVisitorMode(0), RecVisitorBR(nullptr), Ctx(nullptr), PP(pp), - OutDir(outdir), Opts(opts), Plugins(plugins), Injector(injector) { + OutDir(outdir), Opts(opts), Plugins(plugins), Injector(injector), CI(CI) { DigestAnalyzerOptions(); if (Opts->PrintStats) { llvm::EnableStatistics(); @@ -383,6 +386,10 @@ /// \brief Check if we should skip (not analyze) the given function. AnalysisMode getModeForDecl(Decl *D, AnalysisMode Mode); + /// \brief Merge attributes from model files' declarations. + /// Iterates over all local TU declarations. + void MergeModelAttributes(AnalysisDeclContextManager &ADCMgr); + }; } // end anonymous namespace @@ -500,6 +507,42 @@ } } +void AnalysisConsumer::MergeModelAttributes( + AnalysisDeclContextManager &ADCMgr) { + // For all local TU function declarations, check if there is a corresponding + // model's declaration. If yes, merge its attributes, using Sema to handle + // conflicts. + assert(CI.hasSema() && "Compiler instance should have Sema at this point."); + Sema &S = CI.getSema(); + const unsigned LocalTUDeclsSize = LocalTUDecls.size(); + for (unsigned i = 0; i < LocalTUDeclsSize; ++i) { + Decl *D = LocalTUDecls[i]; + FunctionDecl *NewFD = dyn_cast(D); + if (!NewFD) + continue; + AnalysisDeclContext *ADC = ADCMgr.getContext(NewFD); + FunctionDecl *OldFD = ADC->getModelDecl(); + if (!OldFD) // No matching model declarations found. + continue; + NamedDecl *OldND = cast(OldFD); + // Merge the declarations. + S.MergeFunctionDecl(NewFD, OldND, + S.getScopeForContext(NewFD->getDeclContext()), + true /*MergeTypeWithOld*/); + // Sema wont merge parameters' attributes if the old declaration does not + // already have attributes. So we do it manually here. + unsigned NewNumParams = NewFD->getNumParams(); + unsigned OldNewNumParams = OldFD->getNumParams(); + if (NewNumParams != OldNewNumParams) + return; + for (unsigned i = 0; i < NewNumParams; i++) { + ParmVarDecl *NewP = NewFD->getParamDecl(i); + ParmVarDecl *OldP = OldFD->getParamDecl(i); + S.mergeDeclAttributes(NewP, OldP); + } + } +} + void AnalysisConsumer::HandleTranslationUnit(ASTContext &C) { // Don't run the actions if an error has occurred with parsing the file. DiagnosticsEngine &Diags = PP.getDiagnostics(); @@ -514,9 +557,16 @@ { if (TUTotalTimer) TUTotalTimer->startTimer(); + // If "faux-attributes=true" is given, merge model's attributes. + AnalysisDeclContextManager &ADCMgr = Mgr->getAnalysisDeclContextManager(); + if (ADCMgr.mergeModelAttributes()) { + MergeModelAttributes(ADCMgr); + } + // Introduce a scope to destroy BR before Mgr. BugReporter BR(*Mgr); TranslationUnitDecl *TU = C.getTranslationUnitDecl(); + checkerMgr->runCheckersOnASTDecl(TU, *Mgr, BR); // Run the AST-only checks using the order in which functions are defined. @@ -706,7 +756,7 @@ return llvm::make_unique( CI.getPreprocessor(), CI.getFrontendOpts().OutputFile, analyzerOpts, CI.getFrontendOpts().Plugins, - hasModelPath ? new ModelInjector(CI) : nullptr); + hasModelPath ? new ModelInjector(CI) : nullptr, CI); } //===----------------------------------------------------------------------===// Index: lib/StaticAnalyzer/Frontend/FrontendActions.cpp =================================================================== --- lib/StaticAnalyzer/Frontend/FrontendActions.cpp +++ lib/StaticAnalyzer/Frontend/FrontendActions.cpp @@ -18,11 +18,13 @@ return CreateAnalysisConsumer(CI); } -ParseModelFileAction::ParseModelFileAction(llvm::StringMap &Bodies) - : Bodies(Bodies) {} +ParseModelFileAction::ParseModelFileAction( + llvm::StringMap &Bodies, + llvm::StringMap &Decls) + : Bodies(Bodies), Decls(Decls) {} std::unique_ptr ParseModelFileAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { - return llvm::make_unique(Bodies); + return llvm::make_unique(Bodies, Decls); } Index: lib/StaticAnalyzer/Frontend/ModelConsumer.cpp =================================================================== --- lib/StaticAnalyzer/Frontend/ModelConsumer.cpp +++ lib/StaticAnalyzer/Frontend/ModelConsumer.cpp @@ -26,16 +26,22 @@ using namespace clang; using namespace ento; -ModelConsumer::ModelConsumer(llvm::StringMap &Bodies) - : Bodies(Bodies) {} +ModelConsumer::ModelConsumer(llvm::StringMap &Bodies, + llvm::StringMap &Decls) + : Bodies(Bodies), Decls(Decls) {} bool ModelConsumer::HandleTopLevelDecl(DeclGroupRef D) { for (DeclGroupRef::iterator I = D.begin(), E = D.end(); I != E; ++I) { - // Only interested in definitions. - const FunctionDecl *func = llvm::dyn_cast(*I); - if (func && func->hasBody()) { - Bodies.insert(std::make_pair(func->getName(), func->getBody())); + // We are interested in definitions and declarations. + // We cant have constness due to Sema's merging methods taking non-const + // params. + FunctionDecl *func = llvm::dyn_cast(*I); + if (func) { + Decls.insert(std::make_pair(func->getName(), func)); + if (func->hasBody()) { + Bodies.insert(std::make_pair(func->getName(), func->getBody())); + } } } return true; Index: lib/StaticAnalyzer/Frontend/ModelInjector.h =================================================================== --- lib/StaticAnalyzer/Frontend/ModelInjector.h +++ lib/StaticAnalyzer/Frontend/ModelInjector.h @@ -45,6 +45,7 @@ ModelInjector(CompilerInstance &CI); Stmt *getBody(const FunctionDecl *D) override; Stmt *getBody(const ObjCMethodDecl *D) override; + FunctionDecl *getModelDecl(const FunctionDecl *D) override; private: /// \brief Synthesize a body for a declaration @@ -67,6 +68,9 @@ // FIXME: double memoization is redundant, with memoization both here and in // BodyFarm. llvm::StringMap Bodies; + + // Store the model's function declaration if provided. + llvm::StringMap Decls; }; } } Index: lib/StaticAnalyzer/Frontend/ModelInjector.cpp =================================================================== --- lib/StaticAnalyzer/Frontend/ModelInjector.cpp +++ lib/StaticAnalyzer/Frontend/ModelInjector.cpp @@ -37,11 +37,16 @@ return Bodies[D->getName()]; } +FunctionDecl *ModelInjector::getModelDecl(const FunctionDecl *D) { + onBodySynthesis(D); + return Decls[D->getName()]; +} + void ModelInjector::onBodySynthesis(const NamedDecl *D) { // FIXME: what about overloads? Declarations can be used as keys but what // about file name index? Mangled names may not be suitable for that either. - if (Bodies.count(D->getName()) != 0) + if (Bodies.count(D->getName()) != 0 || Decls.count(D->getName()) != 0) return; SourceManager &SM = CI.getSourceManager(); @@ -60,6 +65,7 @@ if (!llvm::sys::fs::exists(fileName.str())) { Bodies[D->getName()] = nullptr; + Decls[D->getName()] = nullptr; return; } @@ -95,7 +101,7 @@ Instance.getPreprocessor().InitializeForModelFile(); - ParseModelFileAction parseModelFile(Bodies); + ParseModelFileAction parseModelFile(Bodies, Decls); const unsigned ThreadStackSize = 8 << 20; llvm::CrashRecoveryContext CRC; @@ -103,6 +109,12 @@ CRC.RunSafelyOnThread([&]() { Instance.ExecuteAction(parseModelFile); }, ThreadStackSize); + // Prevent the model file from being parsed again. + if (Bodies.count(D->getName()) == 0) + Bodies[D->getName()] = nullptr; + if (Decls.count(D->getName()) == 0) + Decls[D->getName()] = nullptr; + Instance.getPreprocessor().FinalizeForModelFile(); Instance.resetAndLeakSourceManager(); Index: test/Analysis/Inputs/Models/modelFileHasAttributes.model =================================================================== --- /dev/null +++ test/Analysis/Inputs/Models/modelFileHasAttributes.model @@ -0,0 +1,2 @@ +int modelFileHasAttributes(int *p0, int *p1, int *p2 __attribute__((nonnull)), + int *_Nonnull p3) __attribute__((nonnull(2))); \ No newline at end of file Index: test/Analysis/Inputs/Models/modelFileHasConflicts.model =================================================================== --- /dev/null +++ test/Analysis/Inputs/Models/modelFileHasConflicts.model @@ -0,0 +1,2 @@ +void modelFileHasConflicts(int *_Nullable p) + __attribute__((visibility("protected"))); \ No newline at end of file Index: test/Analysis/analyzer-config.c =================================================================== --- test/Analysis/analyzer-config.c +++ test/Analysis/analyzer-config.c @@ -13,6 +13,7 @@ // CHECK: [config] // CHECK-NEXT: cfg-conditional-static-initializers = true // CHECK-NEXT: cfg-temporary-dtors = false +// CHECK-NEXT: faux-attributes = false // CHECK-NEXT: faux-bodies = true // CHECK-NEXT: graph-trim-interval = 1000 // CHECK-NEXT: inline-lambdas = true @@ -26,5 +27,5 @@ // CHECK-NEXT: mode = deep // CHECK-NEXT: region-store-small-struct-limit = 2 // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 14 +// CHECK-NEXT: num-entries = 15 Index: test/Analysis/analyzer-config.cpp =================================================================== --- test/Analysis/analyzer-config.cpp +++ test/Analysis/analyzer-config.cpp @@ -24,6 +24,7 @@ // CHECK-NEXT: c++-template-inlining = true // CHECK-NEXT: cfg-conditional-static-initializers = true // CHECK-NEXT: cfg-temporary-dtors = false +// CHECK-NEXT: faux-attributes = false // CHECK-NEXT: faux-bodies = true // CHECK-NEXT: graph-trim-interval = 1000 // CHECK-NEXT: inline-lambdas = true @@ -37,4 +38,4 @@ // CHECK-NEXT: mode = deep // CHECK-NEXT: region-store-small-struct-limit = 2 // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 19 +// CHECK-NEXT: num-entries = 20 Index: test/Analysis/model-attributes.cpp =================================================================== --- /dev/null +++ test/Analysis/model-attributes.cpp @@ -0,0 +1,49 @@ +// This is testing the 'faux-attributes' analyzer option. +// The declaration of 'modelFileHasAttributes' found in +// modelFileHasAttributes.model has 'nonnull' attributes on the 2nd and 3rd +// parameter, and a '_Nonnull' attribute on the 4th parameter. +// The declaration of 'modelFileHasConflicts' has a visibility 'protected' +// attribute and a '_Nullable' parameter attribute. +// RUN: %clang_cc1 -fsyntax-only -triple x86_64-unknown-unknown -analyze -analyzer-checker=core,nullability -analyzer-config faux-attributes=true,model-path=%S/Inputs/Models %s -verify + +int modelFileHasAttributes(int *p0, int *p1, int *p2, int *p3); +void modelFileHasConflicts(int *_Nonnull p) __attribute__((visibility("hidden"))); +// expected-warning@-1 {{nullability specifier '_Nonnull' conflicts with existing specifier '_Nullable'}} +// expected-note@-2 {{previous attribute is here}} +// expected-note@-3 {{previous declaration is here}} + +int f0(int *x, int *y, int *z) { + int *p = 0; + modelFileHasAttributes(p, x, y, z); // no-warning + return 0; +} + +int f1(int *x, int *y, int *z) { + int *p = 0; + modelFileHasAttributes(x, p, y, z); + // expected-warning@-1{{Null pointer passed as an argument to a 'nonnull' parameter}} + return 0; +} + +int f2(int *x, int *y, int *z) { + int *p = 0; + modelFileHasAttributes(x, y, p, z); + // expected-warning@-1{{Null pointer passed as an argument to a 'nonnull' parameter}} + return 0; +} + +int f2a(int *x, int *y, int *z) { + int *p = 0; + modelFileHasAttributes(x, y, z, p); + // expected-warning@-1{{Null passed to a callee that requires a non-null argument}} + return 0; +} + +int f3() { + modelFileHasConflicts(0); + // expected-warning@-1 {{null passed to a callee that requires a non-null argument}} + // expected-error@./Inputs/Models/modelFileHasConflicts.model:2 {{visibility does not match previous declaration}} + // expected-warning@./Inputs/Models/modelFileHasConflicts.model:1 {{nullability specifier '_Nullable' conflicts with existing specifier '_Nonnull'}} + // expected-note@./Inputs/Models/modelFileHasConflicts.model:1 {{previous declaration is here}} + return 0; +}