From: Ted Kremenek Date: Wed, 27 Aug 2014 15:14:15 +0000 (+0000) Subject: Add support for the static analyzer to synthesize function implementations from exter... X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=fdf0d3513240fd8e4da6942e9cd26d2d730eb37b;p=clang 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 .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. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@216550 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Analysis/AnalysisContext.h b/include/clang/Analysis/AnalysisContext.h index 08e335418a..f0f4fda99e 100644 --- a/include/clang/Analysis/AnalysisContext.h +++ b/include/clang/Analysis/AnalysisContext.h @@ -17,8 +17,10 @@ #include "clang/AST/Decl.h" #include "clang/Analysis/CFG.h" +#include "clang/Analysis/CodeInjector.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/OwningPtr.h" #include "llvm/Support/Allocator.h" #include @@ -143,6 +145,14 @@ public: /// \sa getBody bool isBodyAutosynthesized() const; + /// \brief Checks if the body of the Decl is generated by the BodyFarm from a + /// model file. + /// + /// Note, the lookup is not free. We are going to call getBody behind + /// the scenes. + /// \sa getBody + bool isBodyAutosynthesizedFromModelFile() const; + CFG *getCFG(); CFGStmtMap *getCFGStmtMap(); @@ -398,6 +408,10 @@ class AnalysisDeclContextManager { ContextMap Contexts; LocationContextManager LocContexts; CFG::BuildOptions cfgBuildOptions; + + /// Pointer to an interface that can provide function bodies for + /// declarations from external source. + llvm::OwningPtr Injector; /// Flag to indicate whether or not bodies should be synthesized /// for well-known functions. @@ -410,7 +424,8 @@ public: bool addTemporaryDtors = false, bool synthesizeBodies = false, bool addStaticInitBranches = false, - bool addCXXNewAllocator = true); + bool addCXXNewAllocator = true, + CodeInjector* injector = nullptr); ~AnalysisDeclContextManager(); diff --git a/include/clang/Analysis/CodeInjector.h b/include/clang/Analysis/CodeInjector.h new file mode 100644 index 0000000000..c6d6f674ba --- /dev/null +++ b/include/clang/Analysis/CodeInjector.h @@ -0,0 +1,46 @@ +//===-- CodeInjector.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 Defines the clang::CodeInjector interface which is responsible for +/// injecting AST of function definitions that may not be available in the +/// original source. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_CODEINJECTOR_H +#define LLVM_CLANG_ANALYSIS_CODEINJECTOR_H + +namespace clang { + +class Stmt; +class FunctionDecl; +class ObjCMethodDecl; + +/// \brief CodeInjector is an interface which is responsible for injecting AST +/// of function definitions that may not be available in the original source. +/// +/// The getBody function will be called each time the static analyzer examines a +/// function call that has no definition available in the current translation +/// unit. If the returned statement is not a null pointer, it is assumed to be +/// the body of a function which will be used for the analysis. The source of +/// the body can be arbitrary, but it is advised to use memoization to avoid +/// unnecessary reparsing of the external source that provides the body of the +/// functions. +class CodeInjector { +public: + CodeInjector(); + virtual ~CodeInjector(); + + virtual Stmt *getBody(const FunctionDecl *D) = 0; + virtual Stmt *getBody(const ObjCMethodDecl *D) = 0; +}; +} + +#endif \ No newline at end of file diff --git a/include/clang/Basic/SourceManager.h b/include/clang/Basic/SourceManager.h index 03b301d5ca..fb65c4b28d 100644 --- a/include/clang/Basic/SourceManager.h +++ b/include/clang/Basic/SourceManager.h @@ -754,7 +754,6 @@ public: /// \brief Set the file ID for the main source file. void setMainFileID(FileID FID) { - assert(MainFileID.isInvalid() && "MainFileID already set!"); MainFileID = FID; } diff --git a/include/clang/Frontend/FrontendAction.h b/include/clang/Frontend/FrontendAction.h index fa701331f4..c407ff80ac 100644 --- a/include/clang/Frontend/FrontendAction.h +++ b/include/clang/Frontend/FrontendAction.h @@ -157,6 +157,13 @@ public: /// @name Supported Modes /// @{ + /// \brief Is this action invoked on a model file? + /// + /// Model files are incomplete translation units that relies on type + /// information from another translation unit. Check ParseModelFileAction for + /// details. + virtual bool isModelParsingAction() const { return false; } + /// \brief Does this action only use the preprocessor? /// /// If so no AST context will be created and this action will be invalid @@ -224,6 +231,7 @@ protected: void ExecuteAction() override; public: + ASTFrontendAction() {} bool usesPreprocessorOnly() const override { return false; } }; diff --git a/include/clang/Lex/Preprocessor.h b/include/clang/Lex/Preprocessor.h index f61ba9e411..e1896e4789 100644 --- a/include/clang/Lex/Preprocessor.h +++ b/include/clang/Lex/Preprocessor.h @@ -194,6 +194,10 @@ class Preprocessor : public RefCountedBase { /// with this preprocessor. PragmaNamespace *PragmaHandlers; + /// \brief Pragma handlers of the original source is stored here during the + /// parsing of a model file. + PragmaNamespace *PragmaHandlersBackup; + /// \brief Tracks all of the comment handlers that the client registered /// with this preprocessor. std::vector CommentHandlers; @@ -464,6 +468,17 @@ public: /// lifetime of the preprocessor. void Initialize(const TargetInfo &Target); + /// \brief Initialize the preprocessor to parse a model file + /// + /// To parse model files the preprocessor of the original source is reused to + /// preserver the identifier table. However to avoid some duplicate + /// information in the preprocessor some cleanup is needed before it is used + /// to parse model files. This method does that cleanup. + void InitializeForModelFile(); + + /// \brief Cleanup after model file parsing + void FinalizeForModelFile(); + /// \brief Retrieve the preprocessor options used to initialize this /// preprocessor. PreprocessorOptions &getPreprocessorOpts() const { return *PPOpts; } diff --git a/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h b/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h index 9bed9e8369..dbc59cfbd9 100644 --- a/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h +++ b/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h @@ -23,6 +23,8 @@ namespace clang { +class CodeInjector; + namespace ento { class CheckerManager; @@ -50,7 +52,8 @@ public: StoreManagerCreator storemgr, ConstraintManagerCreator constraintmgr, CheckerManager *checkerMgr, - AnalyzerOptions &Options); + AnalyzerOptions &Options, + CodeInjector* injector = nullptr); ~AnalysisManager(); diff --git a/include/clang/StaticAnalyzer/Frontend/AnalysisConsumer.h b/include/clang/StaticAnalyzer/Frontend/AnalysisConsumer.h index 9b12623685..37ea05fb99 100644 --- a/include/clang/StaticAnalyzer/Frontend/AnalysisConsumer.h +++ b/include/clang/StaticAnalyzer/Frontend/AnalysisConsumer.h @@ -25,6 +25,8 @@ namespace clang { class Preprocessor; class DiagnosticsEngine; +class CodeInjector; +class CompilerInstance; namespace ento { class CheckerManager; @@ -38,8 +40,7 @@ public: /// analysis passes. (The set of analyses run is controlled by command-line /// options.) std::unique_ptr -CreateAnalysisConsumer(const Preprocessor &pp, const std::string &output, - AnalyzerOptionsRef opts, ArrayRef plugins); +CreateAnalysisConsumer(CompilerInstance &CI); } // end GR namespace diff --git a/include/clang/StaticAnalyzer/Frontend/FrontendActions.h b/include/clang/StaticAnalyzer/Frontend/FrontendActions.h index fd3f9a80f9..f18b8ccc8b 100644 --- a/include/clang/StaticAnalyzer/Frontend/FrontendActions.h +++ b/include/clang/StaticAnalyzer/Frontend/FrontendActions.h @@ -11,9 +11,13 @@ #define LLVM_CLANG_STATICANALYZER_FRONTEND_FRONTENDACTIONS_H #include "clang/Frontend/FrontendAction.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringMap.h" namespace clang { +class Stmt; + namespace ento { //===----------------------------------------------------------------------===// @@ -26,6 +30,27 @@ protected: StringRef InFile) override; }; +/// \brief Frontend action to parse model files. +/// +/// This frontend action is responsible for parsing model files. Model files can +/// not be parsed on their own, they rely on type information that is available +/// in another translation unit. The parsing of model files is done by a +/// separate compiler instance that reuses the ASTContext and othen information +/// from the main translation unit that is being compiled. After a model file is +/// parsed, the function definitions will be collected into a StringMap. +class ParseModelFileAction : public ASTFrontendAction { +public: + ParseModelFileAction(llvm::StringMap &Bodies); + bool isModelParsingAction() const override { return true; } + +protected: + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override; + +private: + llvm::StringMap &Bodies; +}; + void printCheckerHelp(raw_ostream &OS, ArrayRef plugins); } // end GR namespace diff --git a/include/clang/StaticAnalyzer/Frontend/ModelConsumer.h b/include/clang/StaticAnalyzer/Frontend/ModelConsumer.h new file mode 100644 index 0000000000..24f8042587 --- /dev/null +++ b/include/clang/StaticAnalyzer/Frontend/ModelConsumer.h @@ -0,0 +1,44 @@ +//===-- ModelConsumer.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 implements clang::ento::ModelConsumer which is an +/// ASTConsumer for model files. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_GR_MODELCONSUMER_H +#define LLVM_CLANG_GR_MODELCONSUMER_H + +#include "clang/AST/ASTConsumer.h" +#include "llvm/ADT/StringMap.h" + +namespace clang { + +class Stmt; + +namespace ento { + +/// \brief ASTConsumer to consume model files' AST. +/// +/// This consumer collects the bodies of function definitions into a StringMap +/// from a model file. +class ModelConsumer : public ASTConsumer { +public: + ModelConsumer(llvm::StringMap &Bodies); + + bool HandleTopLevelDecl(DeclGroupRef D) override; + +private: + llvm::StringMap &Bodies; +}; +} +} + +#endif diff --git a/lib/Analysis/AnalysisDeclContext.cpp b/lib/Analysis/AnalysisDeclContext.cpp index 90d4b13b88..5733236f91 100644 --- a/lib/Analysis/AnalysisDeclContext.cpp +++ b/lib/Analysis/AnalysisDeclContext.cpp @@ -69,8 +69,9 @@ AnalysisDeclContextManager::AnalysisDeclContextManager(bool useUnoptimizedCFG, bool addTemporaryDtors, bool synthesizeBodies, bool addStaticInitBranch, - bool addCXXNewAllocator) - : SynthesizeBodies(synthesizeBodies) + bool addCXXNewAllocator, + CodeInjector *injector) + : Injector(injector), SynthesizeBodies(synthesizeBodies) { cfgBuildOptions.PruneTriviallyFalseEdges = !useUnoptimizedCFG; cfgBuildOptions.AddImplicitDtors = addImplicitDtors; @@ -84,8 +85,8 @@ void AnalysisDeclContextManager::clear() { llvm::DeleteContainerSeconds(Contexts); } -static BodyFarm &getBodyFarm(ASTContext &C) { - static BodyFarm *BF = new BodyFarm(C); +static BodyFarm &getBodyFarm(ASTContext &C, CodeInjector *injector = nullptr) { + static BodyFarm *BF = new BodyFarm(C, injector); return *BF; } @@ -94,7 +95,7 @@ Stmt *AnalysisDeclContext::getBody(bool &IsAutosynthesized) const { if (const FunctionDecl *FD = dyn_cast(D)) { Stmt *Body = FD->getBody(); if (!Body && Manager && Manager->synthesizeBodies()) { - Body = getBodyFarm(getASTContext()).getBody(FD); + Body = getBodyFarm(getASTContext(), Manager->Injector.get()).getBody(FD); if (Body) IsAutosynthesized = true; } @@ -103,7 +104,7 @@ Stmt *AnalysisDeclContext::getBody(bool &IsAutosynthesized) const { else if (const ObjCMethodDecl *MD = dyn_cast(D)) { Stmt *Body = MD->getBody(); if (!Body && Manager && Manager->synthesizeBodies()) { - Body = getBodyFarm(getASTContext()).getBody(MD); + Body = getBodyFarm(getASTContext(), Manager->Injector.get()).getBody(MD); if (Body) IsAutosynthesized = true; } @@ -128,6 +129,13 @@ bool AnalysisDeclContext::isBodyAutosynthesized() const { return Tmp; } +bool AnalysisDeclContext::isBodyAutosynthesizedFromModelFile() const { + bool Tmp; + Stmt *Body = getBody(Tmp); + return Tmp && Body->getLocStart().isValid(); +} + + const ImplicitParamDecl *AnalysisDeclContext::getSelfDecl() const { if (const ObjCMethodDecl *MD = dyn_cast(D)) return MD->getSelfDecl(); diff --git a/lib/Analysis/BodyFarm.cpp b/lib/Analysis/BodyFarm.cpp index d4051648f8..23286b2320 100644 --- a/lib/Analysis/BodyFarm.cpp +++ b/lib/Analysis/BodyFarm.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "BodyFarm.h" +#include "clang/Analysis/CodeInjector.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" @@ -381,6 +382,7 @@ Stmt *BodyFarm::getBody(const FunctionDecl *D) { } if (FF) { Val = FF(C, D); } + else if (Injector) { Val = Injector->getBody(D); } return Val.getValue(); } diff --git a/lib/Analysis/BodyFarm.h b/lib/Analysis/BodyFarm.h index 014ada43be..9137943723 100644 --- a/lib/Analysis/BodyFarm.h +++ b/lib/Analysis/BodyFarm.h @@ -27,10 +27,11 @@ class FunctionDecl; class ObjCMethodDecl; class ObjCPropertyDecl; class Stmt; +class CodeInjector; class BodyFarm { public: - BodyFarm(ASTContext &C) : C(C) {} + BodyFarm(ASTContext &C, CodeInjector *injector) : C(C), Injector(injector) {} /// Factory method for creating bodies for ordinary functions. Stmt *getBody(const FunctionDecl *D); @@ -43,6 +44,7 @@ private: ASTContext &C; BodyMap Bodies; + CodeInjector *Injector; }; } diff --git a/lib/Analysis/CMakeLists.txt b/lib/Analysis/CMakeLists.txt index 0f5ec769d6..d913f6668e 100644 --- a/lib/Analysis/CMakeLists.txt +++ b/lib/Analysis/CMakeLists.txt @@ -11,6 +11,7 @@ add_clang_library(clangAnalysis CallGraph.cpp CocoaConventions.cpp Consumed.cpp + CodeInjector.cpp Dominators.cpp DataflowWorklist.cpp FormatString.cpp diff --git a/lib/Analysis/CodeInjector.cpp b/lib/Analysis/CodeInjector.cpp new file mode 100644 index 0000000000..d3766fad69 --- /dev/null +++ b/lib/Analysis/CodeInjector.cpp @@ -0,0 +1,15 @@ +//===-- CodeInjector.cpp ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/CodeInjector.h" + +using namespace clang; + +CodeInjector::CodeInjector() {} +CodeInjector::~CodeInjector() {} \ No newline at end of file diff --git a/lib/Frontend/CompilerInstance.cpp b/lib/Frontend/CompilerInstance.cpp index fa0209887f..a436558890 100644 --- a/lib/Frontend/CompilerInstance.cpp +++ b/lib/Frontend/CompilerInstance.cpp @@ -797,8 +797,9 @@ bool CompilerInstance::ExecuteAction(FrontendAction &Act) { llvm::EnableStatistics(); for (unsigned i = 0, e = getFrontendOpts().Inputs.size(); i != e; ++i) { - // Reset the ID tables if we are reusing the SourceManager. - if (hasSourceManager()) + // Reset the ID tables if we are reusing the SourceManager and parsing + // regular files. + if (hasSourceManager() && !Act.isModelParsingAction()) getSourceManager().clearIDTables(); if (Act.BeginSourceFile(*this, getFrontendOpts().Inputs[i])) { diff --git a/lib/Frontend/FrontendAction.cpp b/lib/Frontend/FrontendAction.cpp index d04169fb2f..8760f5094d 100644 --- a/lib/Frontend/FrontendAction.cpp +++ b/lib/Frontend/FrontendAction.cpp @@ -287,8 +287,10 @@ bool FrontendAction::BeginSourceFile(CompilerInstance &CI, } } - // Set up the preprocessor. - CI.createPreprocessor(getTranslationUnitKind()); + // Set up the preprocessor if needed. When parsing model files the + // preprocessor of the original source is reused. + if (!isModelParsingAction()) + CI.createPreprocessor(getTranslationUnitKind()); // Inform the diagnostic client we are processing a source file. CI.getDiagnosticClient().BeginSourceFile(CI.getLangOpts(), @@ -307,14 +309,18 @@ bool FrontendAction::BeginSourceFile(CompilerInstance &CI, // Create the AST context and consumer unless this is a preprocessor only // action. if (!usesPreprocessorOnly()) { - CI.createASTContext(); + // Parsing a model file should reuse the existing ASTContext. + if (!isModelParsingAction()) + CI.createASTContext(); std::unique_ptr Consumer = CreateWrappedASTConsumer(CI, InputFile); if (!Consumer) goto failure; - CI.getASTContext().setASTMutationListener(Consumer->GetASTMutationListener()); + // FIXME: should not overwrite ASTMutationListener when parsing model files? + if (!isModelParsingAction()) + CI.getASTContext().setASTMutationListener(Consumer->GetASTMutationListener()); if (!CI.getPreprocessorOpts().ChainedIncludes.empty()) { // Convert headers to PCH and chain them. diff --git a/lib/Lex/Preprocessor.cpp b/lib/Lex/Preprocessor.cpp index fd0e48c66c..7d2185ae78 100644 --- a/lib/Lex/Preprocessor.cpp +++ b/lib/Lex/Preprocessor.cpp @@ -187,6 +187,25 @@ void Preprocessor::Initialize(const TargetInfo &Target) { HeaderInfo.setTarget(Target); } +void Preprocessor::InitializeForModelFile() { + NumEnteredSourceFiles = 0; + + // Reset pragmas + PragmaHandlersBackup = PragmaHandlers; + PragmaHandlers = new PragmaNamespace(StringRef()); + RegisterBuiltinPragmas(); + + // Reset PredefinesFileID + PredefinesFileID = FileID(); +} + +void Preprocessor::FinalizeForModelFile() { + NumEnteredSourceFiles = 1; + + delete PragmaHandlers; + PragmaHandlers = PragmaHandlersBackup; +} + void Preprocessor::setPTHManager(PTHManager* pm) { PTH.reset(pm); FileMgr.addStatCache(PTH->createStatCache()); diff --git a/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/lib/StaticAnalyzer/Core/AnalysisManager.cpp index 747b73c416..5798f01370 100644 --- a/lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ b/lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -20,13 +20,16 @@ AnalysisManager::AnalysisManager(ASTContext &ctx, DiagnosticsEngine &diags, StoreManagerCreator storemgr, ConstraintManagerCreator constraintmgr, CheckerManager *checkerMgr, - AnalyzerOptions &Options) + AnalyzerOptions &Options, + CodeInjector *injector) : AnaCtxMgr(Options.UnoptimizedCFG, /*AddImplicitDtors=*/true, /*AddInitializers=*/true, Options.includeTemporaryDtorsInCFG(), Options.shouldSynthesizeBodies(), - Options.shouldConditionalizeStaticInitializers()), + Options.shouldConditionalizeStaticInitializers(), + /*addCXXNewAllocator=*/true, + injector), Ctx(ctx), Diags(diags), LangOpts(lang), diff --git a/lib/StaticAnalyzer/Core/BugReporter.cpp b/lib/StaticAnalyzer/Core/BugReporter.cpp index 141a48ba10..ee274918c2 100644 --- a/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -3247,15 +3247,15 @@ void BugReporter::emitReport(BugReport* R) { // To guarantee memory release. std::unique_ptr UniqueR(R); - // Defensive checking: throw the bug away if it comes from a BodyFarm- - // generated body. We do this very early because report processing relies - // on the report's location being valid. - // FIXME: Valid bugs can occur in BodyFarm-generated bodies, so really we - // need to just find a reasonable location like we do later on with the path - // pieces. if (const ExplodedNode *E = R->getErrorNode()) { - const LocationContext *LCtx = E->getLocationContext(); - if (LCtx->getAnalysisDeclContext()->isBodyAutosynthesized()) + const AnalysisDeclContext *DeclCtx = + E->getLocationContext()->getAnalysisDeclContext(); + // The source of autosynthesized body can be handcrafted AST or a model + // file. The locations from handcrafted ASTs have no valid source locations + // and have to be discarded. Locations from model files should be preserved + // for processing and reporting. + if (DeclCtx->isBodyAutosynthesized() && + !DeclCtx->isBodyAutosynthesizedFromModelFile()) return; } diff --git a/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index c390e9dbfa..4e9ad71785 100644 --- a/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/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 #include @@ -157,6 +160,7 @@ public: const std::string OutDir; AnalyzerOptionsRef Opts; ArrayRef 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 plugins) - : RecVisitorMode(0), RecVisitorBR(nullptr), - Ctx(nullptr), PP(pp), OutDir(outdir), Opts(opts), Plugins(plugins) { + ArrayRef 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 -ento::CreateAnalysisConsumer(const Preprocessor &pp, const std::string &outDir, - AnalyzerOptionsRef opts, - ArrayRef 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(pp, outDir, opts, plugins); + return llvm::make_unique( + CI.getPreprocessor(), CI.getFrontendOpts().OutputFile, analyzerOpts, + CI.getFrontendOpts().Plugins, + hasModelPath ? new ModelInjector(CI) : nullptr); } //===----------------------------------------------------------------------===// diff --git a/lib/StaticAnalyzer/Frontend/CMakeLists.txt b/lib/StaticAnalyzer/Frontend/CMakeLists.txt index 5349ed93e2..76a83681a9 100644 --- a/lib/StaticAnalyzer/Frontend/CMakeLists.txt +++ b/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/lib/StaticAnalyzer/Frontend/FrontendActions.cpp b/lib/StaticAnalyzer/Frontend/FrontendActions.cpp index 567102b765..b33608042c 100644 --- a/lib/StaticAnalyzer/Frontend/FrontendActions.cpp +++ b/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 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 &Bodies) + : Bodies(Bodies) {} + +std::unique_ptr +ParseModelFileAction::CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) { + return llvm::make_unique(Bodies); +} diff --git a/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp b/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp new file mode 100644 index 0000000000..360424bba1 --- /dev/null +++ b/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 &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(*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/lib/StaticAnalyzer/Frontend/ModelInjector.cpp b/lib/StaticAnalyzer/Frontend/ModelInjector.cpp new file mode 100644 index 0000000000..d5b623cd84 --- /dev/null +++ b/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 +#include + +#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 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/lib/StaticAnalyzer/Frontend/ModelInjector.h b/lib/StaticAnalyzer/Frontend/ModelInjector.h new file mode 100644 index 0000000000..9241bd0d4f --- /dev/null +++ b/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 +#include +#include + +#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 Bodies; +}; +} +} + +#endif \ No newline at end of file diff --git a/test/Analysis/Inputs/Models/modeledFunction.model b/test/Analysis/Inputs/Models/modeledFunction.model new file mode 100644 index 0000000000..3aff5fc1b4 --- /dev/null +++ b/test/Analysis/Inputs/Models/modeledFunction.model @@ -0,0 +1,3 @@ +void modelled(intptr p) { + ++*p; +} \ No newline at end of file diff --git a/test/Analysis/Inputs/Models/notzero.model b/test/Analysis/Inputs/Models/notzero.model new file mode 100644 index 0000000000..26161029d6 --- /dev/null +++ b/test/Analysis/Inputs/Models/notzero.model @@ -0,0 +1,3 @@ +bool notzero(int i) { + return i != 0; +} \ No newline at end of file diff --git a/test/Analysis/model-file.cpp b/test/Analysis/model-file.cpp new file mode 100644 index 0000000000..24d6e9312d --- /dev/null +++ b/test/Analysis/model-file.cpp @@ -0,0 +1,288 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=core -analyzer-config faux-bodies=true,model-path=%S/Inputs/Models -analyzer-output=plist-multi-file -verify %s -o %t +// RUN: FileCheck --input-file=%t %s + +typedef int* intptr; + +// This function is modeled and the p pointer is dereferenced in the model +// function and there is no function definition available. The modeled +// function can use any types that are available in the original translation +// unit, for example intptr in this case. +void modeledFunction(intptr p); + +// This function is modeled and returns true if the parameter is not zero +// and there is no function definition available. +bool notzero(int i); + +// This functions is not modeled and there is no function definition. +// available +bool notzero_notmodeled(int i); + +int main() { + // There is a nullpointer dereference inside this function. + modeledFunction(0); + + int p = 0; + if (notzero(p)) { + // It is known that p != 0 because of the information provided by the + // model of the notzero function. + int j = 5 / p; + } + + if (notzero_notmodeled(p)) { + // There is no information about the value of p, because + // notzero_notmodeled is not modeled and the function definition + // is not available. + int j = 5 / p; // expected-warning {{Division by zero}} + } + + return 0; +} + +// CHECK: diagnostics +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: path +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindcontrol +// CHECK-NEXT: edges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: start +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line22 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line22 +// CHECK-NEXT: col17 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: end +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line24 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line24 +// CHECK-NEXT: col5 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindevent +// CHECK-NEXT: location +// CHECK-NEXT: +// CHECK-NEXT: line24 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: ranges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line24 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line24 +// CHECK-NEXT: col7 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: depth0 +// CHECK-NEXT: extended_message +// CHECK-NEXT: 'p' initialized to 0 +// CHECK-NEXT: message +// CHECK-NEXT: 'p' initialized to 0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindcontrol +// CHECK-NEXT: edges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: start +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line24 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line24 +// CHECK-NEXT: col5 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: end +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line25 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line25 +// CHECK-NEXT: col4 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindcontrol +// CHECK-NEXT: edges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: start +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line25 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line25 +// CHECK-NEXT: col4 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: end +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line31 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line31 +// CHECK-NEXT: col4 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindcontrol +// CHECK-NEXT: edges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: start +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line31 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line31 +// CHECK-NEXT: col4 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: end +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line31 +// CHECK-NEXT: col7 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line31 +// CHECK-NEXT: col24 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindcontrol +// CHECK-NEXT: edges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: start +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line31 +// CHECK-NEXT: col7 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line31 +// CHECK-NEXT: col24 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: end +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line35 +// CHECK-NEXT: col15 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line35 +// CHECK-NEXT: col15 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindevent +// CHECK-NEXT: location +// CHECK-NEXT: +// CHECK-NEXT: line35 +// CHECK-NEXT: col15 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: ranges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line35 +// CHECK-NEXT: col13 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line35 +// CHECK-NEXT: col17 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: depth0 +// CHECK-NEXT: extended_message +// CHECK-NEXT: Division by zero +// CHECK-NEXT: message +// CHECK-NEXT: Division by zero +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: descriptionDivision by zero +// CHECK-NEXT: categoryLogic error +// CHECK-NEXT: typeDivision by zero +// CHECK-NEXT: issue_context_kindfunction +// CHECK-NEXT: issue_contextmain +// CHECK-NEXT: issue_hash15 +// CHECK-NEXT: location +// CHECK-NEXT: +// CHECK-NEXT: line35 +// CHECK-NEXT: col15 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: \ No newline at end of file