diff options
author | Ted Kremenek <kremenek@apple.com> | 2014-08-27 15:14:15 +0000 |
---|---|---|
committer | Ted Kremenek <kremenek@apple.com> | 2014-08-27 15:14:15 +0000 |
commit | eeccb30b942fbfd220990cbf52335559cdc04959 (patch) | |
tree | 641a7be8d79c887e0ca05c13703511208aa49dba /clang/lib/StaticAnalyzer/Frontend | |
parent | 5dc466b863a4d709747fbf73239ddbbcc47fa417 (diff) | |
download | bcm5719-llvm-eeccb30b942fbfd220990cbf52335559cdc04959.tar.gz bcm5719-llvm-eeccb30b942fbfd220990cbf52335559cdc04959.zip |
Add support for the static analyzer to synthesize function implementations from external model files.
Currently the analyzer lazily models some functions using 'BodyFarm',
which constructs a fake function implementation that the analyzer
can simulate that approximates the semantics of the function when
it is called. BodyFarm does this by constructing the AST for
such definitions on-the-fly. One strength of BodyFarm
is that all symbols and types referenced by synthesized function
bodies are contextual adapted to the containing translation unit.
The downside is that these ASTs are hardcoded in Clang's own
source code.
A more scalable model is to allow these models to be defined as source
code in separate "model" files and have the analyzer use those
definitions lazily when a function body is needed. Among other things,
it will allow more customization of the analyzer for specific APIs
and platforms.
This patch provides the initial infrastructure for this feature.
It extends BodyFarm to use an abstract API 'CodeInjector' that can be
used to synthesize function bodies. That 'CodeInjector' is
implemented using a new 'ModelInjector' in libFrontend, which lazily
parses a model file and injects the ASTs into the current translation
unit.
Models are currently found by specifying a 'model-path' as an
analyzer option; if no path is specified the CodeInjector is not
used, thus defaulting to the current behavior in the analyzer.
Models currently contain a single function definition, and can
be found by finding the file <function name>.model. This is an
initial starting point for something more rich, but it bootstraps
this feature for future evolution.
This patch was contributed by Gábor Horváth as part of his
Google Summer of Code project.
Some notes:
- This introduces the notion of a "model file" into
FrontendAction and the Preprocessor. This nomenclature
is specific to the static analyzer, but possibly could be
generalized. Essentially these are sources pulled in
exogenously from the principal translation.
Preprocessor gets a 'InitializeForModelFile' and
'FinalizeForModelFile' which could possibly be hoisted out
of Preprocessor if Preprocessor exposed a new API to
change the PragmaHandlers and some other internal pieces. This
can be revisited.
FrontendAction gets a 'isModelParsingAction()' predicate function
used to allow a new FrontendAction to recycle the Preprocessor
and ASTContext. This name could probably be made something
more general (i.e., not tied to 'model files') at the expense
of losing the intent of why it exists. This can be revisited.
- This is a moderate sized patch; it has gone through some amount of
offline code review. Most of the changes to the non-analyzer
parts are fairly small, and would make little sense without
the analyzer changes.
- Most of the analyzer changes are plumbing, with the interesting
behavior being introduced by ModelInjector.cpp and
ModelConsumer.cpp.
- The new functionality introduced by this change is off-by-default.
It requires an analyzer config option to enable.
llvm-svn: 216550
Diffstat (limited to 'clang/lib/StaticAnalyzer/Frontend')
-rw-r--r-- | clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp | 29 | ||||
-rw-r--r-- | clang/lib/StaticAnalyzer/Frontend/CMakeLists.txt | 2 | ||||
-rw-r--r-- | clang/lib/StaticAnalyzer/Frontend/FrontendActions.cpp | 15 | ||||
-rw-r--r-- | clang/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp | 42 | ||||
-rw-r--r-- | clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp | 119 | ||||
-rw-r--r-- | clang/lib/StaticAnalyzer/Frontend/ModelInjector.h | 75 |
6 files changed, 268 insertions, 14 deletions
diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index c390e9dbfa5..4e9ad71785a 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -18,6 +18,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/ParentMap.h" +#include "clang/Analysis/CodeInjector.h" #include "clang/Analysis/Analyses/LiveVariables.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CallGraph.h" @@ -33,6 +34,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Frontend/CheckerRegistration.h" +#include "clang/Frontend/CompilerInstance.h" #include "llvm/ADT/DepthFirstIterator.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/SmallPtrSet.h" @@ -42,6 +44,7 @@ #include "llvm/Support/Program.h" #include "llvm/Support/Timer.h" #include "llvm/Support/raw_ostream.h" +#include "ModelInjector.h" #include <memory> #include <queue> @@ -157,6 +160,7 @@ public: const std::string OutDir; AnalyzerOptionsRef Opts; ArrayRef<std::string> Plugins; + CodeInjector *Injector; /// \brief Stores the declarations from the local translation unit. /// Note, we pre-compute the local declarations at parse time as an @@ -184,9 +188,10 @@ public: AnalysisConsumer(const Preprocessor& pp, const std::string& outdir, AnalyzerOptionsRef opts, - ArrayRef<std::string> plugins) - : RecVisitorMode(0), RecVisitorBR(nullptr), - Ctx(nullptr), PP(pp), OutDir(outdir), Opts(opts), Plugins(plugins) { + ArrayRef<std::string> plugins, + CodeInjector *injector) + : RecVisitorMode(0), RecVisitorBR(nullptr), Ctx(nullptr), PP(pp), + OutDir(outdir), Opts(opts), Plugins(plugins), Injector(injector) { DigestAnalyzerOptions(); if (Opts->PrintStats) { llvm::EnableStatistics(); @@ -286,6 +291,7 @@ public: Ctx = &Context; checkerMgr.reset(createCheckerManager(*Opts, PP.getLangOpts(), Plugins, PP.getDiagnostics())); + Mgr.reset(new AnalysisManager(*Ctx, PP.getDiagnostics(), PP.getLangOpts(), @@ -293,7 +299,8 @@ public: CreateStoreMgr, CreateConstraintMgr, checkerMgr.get(), - *Opts)); + *Opts, + Injector)); } /// \brief Store the top level decls in the set to be processed later on. @@ -688,13 +695,17 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, //===----------------------------------------------------------------------===// std::unique_ptr<AnalysisASTConsumer> -ento::CreateAnalysisConsumer(const Preprocessor &pp, const std::string &outDir, - AnalyzerOptionsRef opts, - ArrayRef<std::string> plugins) { +ento::CreateAnalysisConsumer(CompilerInstance &CI) { // Disable the effects of '-Werror' when using the AnalysisConsumer. - pp.getDiagnostics().setWarningsAsErrors(false); + CI.getPreprocessor().getDiagnostics().setWarningsAsErrors(false); + + AnalyzerOptionsRef analyzerOpts = CI.getAnalyzerOpts(); + bool hasModelPath = analyzerOpts->Config.count("model-path") > 0; - return llvm::make_unique<AnalysisConsumer>(pp, outDir, opts, plugins); + return llvm::make_unique<AnalysisConsumer>( + CI.getPreprocessor(), CI.getFrontendOpts().OutputFile, analyzerOpts, + CI.getFrontendOpts().Plugins, + hasModelPath ? new ModelInjector(CI) : nullptr); } //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Frontend/CMakeLists.txt b/clang/lib/StaticAnalyzer/Frontend/CMakeLists.txt index 5349ed93e2c..76a83681a97 100644 --- a/clang/lib/StaticAnalyzer/Frontend/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Frontend/CMakeLists.txt @@ -7,7 +7,9 @@ set(LLVM_LINK_COMPONENTS add_clang_library(clangStaticAnalyzerFrontend AnalysisConsumer.cpp CheckerRegistration.cpp + ModelConsumer.cpp FrontendActions.cpp + ModelInjector.cpp LINK_LIBS clangAST diff --git a/clang/lib/StaticAnalyzer/Frontend/FrontendActions.cpp b/clang/lib/StaticAnalyzer/Frontend/FrontendActions.cpp index 567102b7656..b33608042ce 100644 --- a/clang/lib/StaticAnalyzer/Frontend/FrontendActions.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/FrontendActions.cpp @@ -8,16 +8,21 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Frontend/FrontendActions.h" -#include "clang/Frontend/CompilerInstance.h" #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" +#include "clang/StaticAnalyzer/Frontend/ModelConsumer.h" using namespace clang; using namespace ento; std::unique_ptr<ASTConsumer> AnalysisAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { - return CreateAnalysisConsumer(CI.getPreprocessor(), - CI.getFrontendOpts().OutputFile, - CI.getAnalyzerOpts(), - CI.getFrontendOpts().Plugins); + return CreateAnalysisConsumer(CI); } +ParseModelFileAction::ParseModelFileAction(llvm::StringMap<Stmt *> &Bodies) + : Bodies(Bodies) {} + +std::unique_ptr<ASTConsumer> +ParseModelFileAction::CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) { + return llvm::make_unique<ModelConsumer>(Bodies); +} diff --git a/clang/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp new file mode 100644 index 00000000000..360424bba19 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp @@ -0,0 +1,42 @@ +//===--- ModelConsumer.cpp - ASTConsumer for consuming model files --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file implements an ASTConsumer for consuming model files. +/// +/// This ASTConsumer handles the AST of a parsed model file. All top level +/// function definitions will be collected from that model file for later +/// retrieval during the static analysis. The body of these functions will not +/// be injected into the ASTUnit of the analyzed translation unit. It will be +/// available through the BodyFarm which is utilized by the AnalysisDeclContext +/// class. +/// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Frontend/ModelConsumer.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclGroup.h" + +using namespace clang; +using namespace ento; + +ModelConsumer::ModelConsumer(llvm::StringMap<Stmt *> &Bodies) + : Bodies(Bodies) {} + +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<FunctionDecl>(*I); + if (func && func->hasBody()) { + Bodies.insert(std::make_pair(func->getName(), func->getBody())); + } + } + return true; +}
\ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp b/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp new file mode 100644 index 00000000000..d5b623cd840 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp @@ -0,0 +1,119 @@ +//===-- ModelInjector.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ModelInjector.h" + +#include <string> +#include <utility> + +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/StaticAnalyzer/Frontend/FrontendActions.h" +#include "clang/Serialization/ASTReader.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/AST/Decl.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/Support/CrashRecoveryContext.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/ADT/STLExtras.h" + +using namespace clang; +using namespace ento; + +ModelInjector::ModelInjector(CompilerInstance &CI) : CI(CI) {} + +Stmt *ModelInjector::getBody(const FunctionDecl *D) { + onBodySynthesis(D); + return Bodies[D->getName()]; +} + +Stmt *ModelInjector::getBody(const ObjCMethodDecl *D) { + onBodySynthesis(D); + return Bodies[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) + return; + + SourceManager &SM = CI.getSourceManager(); + FileID mainFileID = SM.getMainFileID(); + + AnalyzerOptionsRef analyzerOpts = CI.getAnalyzerOpts(); + llvm::StringRef modelPath = analyzerOpts->Config["model-path"]; + + llvm::SmallString<128> fileName; + + if (!modelPath.empty()) + fileName = + llvm::StringRef(modelPath.str() + "/" + D->getName().str() + ".model"); + else + fileName = llvm::StringRef(D->getName().str() + ".model"); + + if (!llvm::sys::fs::exists(fileName.str())) { + Bodies[D->getName()] = nullptr; + return; + } + + IntrusiveRefCntPtr<CompilerInvocation> Invocation( + new CompilerInvocation(CI.getInvocation())); + + FrontendOptions &FrontendOpts = Invocation->getFrontendOpts(); + InputKind IK = IK_CXX; // FIXME + FrontendOpts.Inputs.clear(); + FrontendOpts.Inputs.push_back(FrontendInputFile(fileName, IK)); + FrontendOpts.DisableFree = true; + + Invocation->getDiagnosticOpts().VerifyDiagnostics = 0; + + // Modules are parsed by a separate CompilerInstance, so this code mimics that + // behavior for models + CompilerInstance Instance; + Instance.setInvocation(&*Invocation); + Instance.createDiagnostics( + new ForwardingDiagnosticConsumer(CI.getDiagnosticClient()), + /*ShouldOwnClient=*/true); + + Instance.getDiagnostics().setSourceManager(&SM); + + Instance.setVirtualFileSystem(&CI.getVirtualFileSystem()); + + // The instance wants to take ownership, however DisableFree frontend option + // is set to true to avoid double free issues + Instance.setFileManager(&CI.getFileManager()); + Instance.setSourceManager(&SM); + Instance.setPreprocessor(&CI.getPreprocessor()); + Instance.setASTContext(&CI.getASTContext()); + + Instance.getPreprocessor().InitializeForModelFile(); + + ParseModelFileAction parseModelFile(Bodies); + + const unsigned ThreadStackSize = 8 << 20; + llvm::CrashRecoveryContext CRC; + + CRC.RunSafelyOnThread([&]() { Instance.ExecuteAction(parseModelFile); }, + ThreadStackSize); + + Instance.getPreprocessor().FinalizeForModelFile(); + + Instance.resetAndLeakSourceManager(); + Instance.resetAndLeakFileManager(); + Instance.resetAndLeakPreprocessor(); + + // The preprocessor enters to the main file id when parsing is started, so + // the main file id is changed to the model file during parsing and it needs + // to be reseted to the former main file id after parsing of the model file + // is done. + SM.setMainFileID(mainFileID); +} diff --git a/clang/lib/StaticAnalyzer/Frontend/ModelInjector.h b/clang/lib/StaticAnalyzer/Frontend/ModelInjector.h new file mode 100644 index 00000000000..9241bd0d4fe --- /dev/null +++ b/clang/lib/StaticAnalyzer/Frontend/ModelInjector.h @@ -0,0 +1,75 @@ +//===-- ModelInjector.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file defines the clang::ento::ModelInjector class which implements the +/// clang::CodeInjector interface. This class is responsible for injecting +/// function definitions that were synthesized from model files. +/// +/// Model files allow definitions of functions to be lazily constituted for functions +/// which lack bodies in the original source code. This allows the analyzer +/// to more precisely analyze code that calls such functions, analyzing the +/// artificial definitions (which typically approximate the semantics of the +/// called function) when called by client code. These definitions are +/// reconstituted lazily, on-demand, by the static analyzer engine. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SA_FRONTEND_MODELINJECTOR_H +#define LLVM_CLANG_SA_FRONTEND_MODELINJECTOR_H + +#include <map> +#include <vector> +#include <memory> + +#include "clang/Analysis/CodeInjector.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/StringMap.h" + +namespace clang { + +class CompilerInstance; +class ASTUnit; +class ASTReader; +class NamedDecl; +class Module; + +namespace ento { +class ModelInjector : public CodeInjector { +public: + ModelInjector(CompilerInstance &CI); + Stmt *getBody(const FunctionDecl *D); + Stmt *getBody(const ObjCMethodDecl *D); + +private: + /// \brief Synthesize a body for a declaration + /// + /// This method first looks up the appropriate model file based on the + /// model-path configuration option and the name of the declaration that is + /// looked up. If no model were synthesized yet for a function with that name + /// it will create a new compiler instance to parse the model file using the + /// ASTContext, Preprocessor, SourceManager of the original compiler instance. + /// The former resources are shared between the two compiler instance, so the + /// newly created instance have to "leak" these objects, since they are owned + /// by the original instance. + /// + /// The model-path should be either an absolute path or relative to the + /// working directory of the compiler. + void onBodySynthesis(const NamedDecl *D); + + CompilerInstance &CI; + + // FIXME: double memoization is redundant, with memoization both here and in + // BodyFarm. + llvm::StringMap<Stmt *> Bodies; +}; +} +} + +#endif
\ No newline at end of file |