Index: include/clang/Analysis/AnalysisContext.h =================================================================== --- include/clang/Analysis/AnalysisContext.h +++ include/clang/Analysis/AnalysisContext.h @@ -152,6 +152,11 @@ /// \sa getBody bool isBodyAutosynthesizedFromModelFile() const; + /// \brief Merge the attributes found in model files. + /// Note, this adds all attributes found in the model file without any sanity + /// checks. + void MergeModelAttributes(); + CFG *getCFG(); CFGStmtMap *getCFGStmtMap(); @@ -416,12 +421,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 +451,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,13 @@ return Tmp && Body->getLocStart().isValid(); } +void AnalysisDeclContext::MergeModelAttributes() { + if (const FunctionDecl *FD = dyn_cast(D)) { + if (Manager) { + getBodyFarm(getASTContext(), Manager->Injector.get()).MergeModelAttr(FD); + } + } +} 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,12 +39,22 @@ /// Factory method for creating bodies for Objective-C properties. Stmt *getBody(const ObjCMethodDecl *D); + /// \brief Merge the attributes found in model files. + /// Note, this adds all attributes found in the model file without any sanity + /// checks. + void MergeModelAttr(const FunctionDecl *FD); + private: typedef llvm::DenseMap > BodyMap; + typedef llvm::DenseMap > DeclsMap; ASTContext &C; BodyMap Bodies; + DeclsMap Decls; CodeInjector *Injector; + + /// Returns the model's declaration. + FunctionDecl *getModelDecl(const FunctionDecl *D); }; } Index: lib/Analysis/BodyFarm.cpp =================================================================== --- lib/Analysis/BodyFarm.cpp +++ lib/Analysis/BodyFarm.cpp @@ -386,6 +386,64 @@ return Val.getValue(); } +void BodyFarm::MergeModelAttr(const FunctionDecl *FD) { + // Merge attributes attached to the function declaration. + Decl *ModelDecl = getModelDecl(FD); + if (!ModelDecl) + return; + FunctionDecl * ModelFD = cast(ModelDecl); + Optional ModelFDAttrs; + if (ModelFD->hasAttrs()) + ModelFDAttrs = ModelFD->getAttrs(); + FunctionDecl *CastFD = const_cast(FD); + if (ModelFDAttrs.hasValue()) { + AttrVec& AV = *ModelFDAttrs; + AttrVec::iterator i = AV.begin(), e = AV.end(); + for (; i != e; ++i) { + CastFD->addAttr(*i); + } + } + // Merge attributes attached to the function parameters. + if (!ModelFD) + return; + unsigned int NumParam = CastFD->getNumParams(); + if (NumParam != ModelFD->getNumParams()) + return; + for (unsigned k = 0; k < NumParam; k++) { + Decl * ModelParamDecl = ModelFD->getParamDecl(k); + if (!ModelParamDecl || (ModelParamDecl && !ModelParamDecl->hasAttrs())) + continue; + Decl * ParamDecl = CastFD->getParamDecl(k); + if (!ParamDecl) + continue; + AttrVec& AV = ModelParamDecl->getAttrs(); + AttrVec::iterator i = AV.begin(), e = AV.end(); + for (; i != e; ++i) { + ParamDecl->addAttr(*i); + } + } +} + +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 @@ -500,6 +500,25 @@ } } +namespace { +// AST visitor used to merge model attributes. +class WalkAST : public DataRecursiveASTVisitor { + AnalysisDeclContextManager &Mgr; + +public: + WalkAST(AnalysisDeclContextManager &Mgr) : Mgr(Mgr) {} + + // Merge model attributes for all call expressions. + bool VisitCallExpr(CallExpr *CE) { + if (FunctionDecl *FD = CE->getDirectCallee()) { + AnalysisDeclContext *ADC = Mgr.getContext(FD); + ADC->MergeModelAttributes(); + } + return true; + } +}; +} // end anonymous namespace + void AnalysisConsumer::HandleTranslationUnit(ASTContext &C) { // Don't run the actions if an error has occurred with parsing the file. DiagnosticsEngine &Diags = PP.getDiagnostics(); @@ -517,6 +536,14 @@ // Introduce a scope to destroy BR before Mgr. BugReporter BR(*Mgr); TranslationUnitDecl *TU = C.getTranslationUnitDecl(); + + // If "faux-attributes=true" is given, merge model's attributes. + AnalysisDeclContextManager &ADCMgr = Mgr->getAnalysisDeclContextManager(); + if (ADCMgr.mergeModelAttributes()) { + WalkAST W(ADCMgr); + W.TraverseDecl(TU); + } + checkerMgr->runCheckersOnASTDecl(TU, *Mgr, BR); // Run the AST-only checks using the order in which functions are defined. Index: lib/StaticAnalyzer/Frontend/FrontendActions.cpp =================================================================== --- lib/StaticAnalyzer/Frontend/FrontendActions.cpp +++ lib/StaticAnalyzer/Frontend/FrontendActions.cpp @@ -18,11 +18,12 @@ 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,20 @@ 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. + 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; Index: test/Analysis/Inputs/Models/modelFileHasAttributes.model =================================================================== --- /dev/null +++ test/Analysis/Inputs/Models/modelFileHasAttributes.model @@ -0,0 +1 @@ +int modelFileHasAttributes(int * p0, int * p1, int * p2 __attribute__((nonnull))) __attribute__((nonnull(2))); \ 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,23 @@ +// 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. +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -analyze -analyzer-checker=core -analyzer-config faux-attributes=true,model-path=%S/Inputs/Models %s -verify + +int modelFileHasAttributes(int * p0, int * p1, int * p2); + +int f0(int * x, int * y) { + int * p = 0; + modelFileHasAttributes(p, x, y); // no-warning + return 0; +} + +int f1(int * x, int * y) { + int * p = 0; + modelFileHasAttributes(x, p, y); // expected-warning{{Null pointer passed as an argument to a 'nonnull' parameter}} + return 0; +} + +int f2(int * x, int * y) { + int * p = 0; + modelFileHasAttributes(x, y, p); // expected-warning{{Null pointer passed as an argument to a 'nonnull' parameter}} + return 0; +}