]> granicus.if.org Git - clang/commitdiff
Add support for the static analyzer to synthesize function implementations from exter...
authorTed Kremenek <kremenek@apple.com>
Wed, 27 Aug 2014 15:14:15 +0000 (15:14 +0000)
committerTed Kremenek <kremenek@apple.com>
Wed, 27 Aug 2014 15:14:15 +0000 (15:14 +0000)
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.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@216550 91177308-0d34-0410-b5e6-96231b3b80d8

28 files changed:
include/clang/Analysis/AnalysisContext.h
include/clang/Analysis/CodeInjector.h [new file with mode: 0644]
include/clang/Basic/SourceManager.h
include/clang/Frontend/FrontendAction.h
include/clang/Lex/Preprocessor.h
include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h
include/clang/StaticAnalyzer/Frontend/AnalysisConsumer.h
include/clang/StaticAnalyzer/Frontend/FrontendActions.h
include/clang/StaticAnalyzer/Frontend/ModelConsumer.h [new file with mode: 0644]
lib/Analysis/AnalysisDeclContext.cpp
lib/Analysis/BodyFarm.cpp
lib/Analysis/BodyFarm.h
lib/Analysis/CMakeLists.txt
lib/Analysis/CodeInjector.cpp [new file with mode: 0644]
lib/Frontend/CompilerInstance.cpp
lib/Frontend/FrontendAction.cpp
lib/Lex/Preprocessor.cpp
lib/StaticAnalyzer/Core/AnalysisManager.cpp
lib/StaticAnalyzer/Core/BugReporter.cpp
lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
lib/StaticAnalyzer/Frontend/CMakeLists.txt
lib/StaticAnalyzer/Frontend/FrontendActions.cpp
lib/StaticAnalyzer/Frontend/ModelConsumer.cpp [new file with mode: 0644]
lib/StaticAnalyzer/Frontend/ModelInjector.cpp [new file with mode: 0644]
lib/StaticAnalyzer/Frontend/ModelInjector.h [new file with mode: 0644]
test/Analysis/Inputs/Models/modeledFunction.model [new file with mode: 0644]
test/Analysis/Inputs/Models/notzero.model [new file with mode: 0644]
test/Analysis/model-file.cpp [new file with mode: 0644]

index 08e335418ab759119864bc62069536969f9524cb..f0f4fda99e3e1abae31332c1d464bc7808ba17a8 100644 (file)
 
 #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 <memory>
 
@@ -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<CodeInjector> 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 (file)
index 0000000..c6d6f67
--- /dev/null
@@ -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
index 03b301d5ca47b32bfa7035788a9f652834a38c9a..fb65c4b28d0f8b875b371fbc9aeff49c52a994a8 100644 (file)
@@ -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;
   }
 
index fa701331f4f79e63b85209631b6a7cc1c0d5b21e..c407ff80ac56260a5c0b8c033835bdbaf77a589c 100644 (file)
@@ -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; }
 };
 
index f61ba9e41175748c7bb71bd5b25878e0df5a0427..e1896e4789da3e121640a8b783ffbddd32197f83 100644 (file)
@@ -194,6 +194,10 @@ class Preprocessor : public RefCountedBase<Preprocessor> {
   /// 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<CommentHandler *> 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; }
index 9bed9e8369c5ddfb36fb47f32de531721359b94c..dbc59cfbd9d7eeee4ad0850adc2115cf37f8d34b 100644 (file)
@@ -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();
   
index 9b126236858920ce94e8b4fe3f474313f9dba1f2..37ea05fb99cff06c066f201e4fcb721fd779481e 100644 (file)
@@ -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<AnalysisASTConsumer>
-CreateAnalysisConsumer(const Preprocessor &pp, const std::string &output,
-                       AnalyzerOptionsRef opts, ArrayRef<std::string> plugins);
+CreateAnalysisConsumer(CompilerInstance &CI);
 
 } // end GR namespace
 
index fd3f9a80f92b37ccf3588d2bbd5fd108b4d14d25..f18b8ccc8b0bd53a2f30c0d566d42b0c8bf11928 100644 (file)
 #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<Stmt *> &Bodies);
+  bool isModelParsingAction() const override { return true; }
+
+protected:
+  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
+                                                 StringRef InFile) override;
+
+private:
+  llvm::StringMap<Stmt *> &Bodies;
+};
+
 void printCheckerHelp(raw_ostream &OS, ArrayRef<std::string> plugins);
 
 } // end GR namespace
diff --git a/include/clang/StaticAnalyzer/Frontend/ModelConsumer.h b/include/clang/StaticAnalyzer/Frontend/ModelConsumer.h
new file mode 100644 (file)
index 0000000..24f8042
--- /dev/null
@@ -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<Stmt *> &Bodies);
+
+  bool HandleTopLevelDecl(DeclGroupRef D) override;
+
+private:
+  llvm::StringMap<Stmt *> &Bodies;
+};
+}
+}
+
+#endif
index 90d4b13b88be2e097e6c0ecb5a6a786901f5ae7a..5733236f91b07f3ad32381b976a4c1e744b75d08 100644 (file)
@@ -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<FunctionDecl>(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<ObjCMethodDecl>(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<ObjCMethodDecl>(D))
     return MD->getSelfDecl();
index d4051648f86cb061bc6b90418acd18a6a63e2e92..23286b2320a8102bc76da01e3b9d3e0598cc247d 100644 (file)
@@ -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();
 }
 
index 014ada43be1b358ea0633b567d841d2e532eefb3..91379437231dc7cf23c000f377424e1fe8d4604f 100644 (file)
@@ -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;
 };
 }
 
index 0f5ec769d698d96805ec666428d14c353a514eee..d913f6668e4d7292f0635a7d1eca2beccfec9552 100644 (file)
@@ -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 (file)
index 0000000..d3766fa
--- /dev/null
@@ -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
index fa0209887ff7511062beeaa463ca1fcec3d98456..a43655889055089d90eb473d5c1416c2e481c589 100644 (file)
@@ -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])) {
index d04169fb2f2270161ecfadc2578db90168a967a7..8760f5094d47d0007c1a84ea8bcce32781a89d56 100644 (file)
@@ -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<ASTConsumer> 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.
index fd0e48c66c85a90fce21803090ae38456da3a225..7d2185ae78207397f0237a9a5fd65e2b9d842110 100644 (file)
@@ -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());
index 747b73c4164b9fc47e942cc67dee31dcf4833534..5798f01370de84c7b86df712c28e074b2eb59582 100644 (file)
@@ -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),
index 141a48ba10b35ec7e7a9942cba24f764545db419..ee274918c255708ce881e5568dc0d12b16ce1f12 100644 (file)
@@ -3247,15 +3247,15 @@ void BugReporter::emitReport(BugReport* R) {
   // To guarantee memory release.
   std::unique_ptr<BugReport> 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;
   }
   
index c390e9dbfa5570ace6bd77aa53f232277c3711b9..4e9ad71785a7d19724248f360efbe12366d24ca5 100644 (file)
@@ -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);
 }
 
 //===----------------------------------------------------------------------===//
index 5349ed93e2c4134a8e5acd401a219c7776364d3a..76a83681a970a3a229e8fe415560232f0bc1569e 100644 (file)
@@ -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
index 567102b7656d8b753e2c5f4dfeac3083ef63eeb8..b33608042ce3d21cdcab44e67b99ec44381faa05 100644 (file)
@@ -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/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp b/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp
new file mode 100644 (file)
index 0000000..360424b
--- /dev/null
@@ -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/lib/StaticAnalyzer/Frontend/ModelInjector.cpp b/lib/StaticAnalyzer/Frontend/ModelInjector.cpp
new file mode 100644 (file)
index 0000000..d5b623c
--- /dev/null
@@ -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/lib/StaticAnalyzer/Frontend/ModelInjector.h b/lib/StaticAnalyzer/Frontend/ModelInjector.h
new file mode 100644 (file)
index 0000000..9241bd0
--- /dev/null
@@ -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
diff --git a/test/Analysis/Inputs/Models/modeledFunction.model b/test/Analysis/Inputs/Models/modeledFunction.model
new file mode 100644 (file)
index 0000000..3aff5fc
--- /dev/null
@@ -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 (file)
index 0000000..2616102
--- /dev/null
@@ -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 (file)
index 0000000..24d6e93
--- /dev/null
@@ -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:  <key>diagnostics</key>
+// CHECK-NEXT:  <array>
+// CHECK-NEXT:  <dict>
+// CHECK-NEXT:   <key>path</key>
+// CHECK-NEXT:   <array>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>kind</key><string>control</string>
+// CHECK-NEXT:     <key>edges</key>
+// CHECK-NEXT:      <array>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>start</key>
+// CHECK-NEXT:         <array>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>22</integer>
+// CHECK-NEXT:           <key>col</key><integer>3</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>22</integer>
+// CHECK-NEXT:           <key>col</key><integer>17</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:         </array>
+// CHECK-NEXT:        <key>end</key>
+// CHECK-NEXT:         <array>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>24</integer>
+// CHECK-NEXT:           <key>col</key><integer>3</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>24</integer>
+// CHECK-NEXT:           <key>col</key><integer>5</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:         </array>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:      </array>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>kind</key><string>event</string>
+// CHECK-NEXT:     <key>location</key>
+// CHECK-NEXT:     <dict>
+// CHECK-NEXT:      <key>line</key><integer>24</integer>
+// CHECK-NEXT:      <key>col</key><integer>3</integer>
+// CHECK-NEXT:      <key>file</key><integer>0</integer>
+// CHECK-NEXT:     </dict>
+// CHECK-NEXT:     <key>ranges</key>
+// CHECK-NEXT:     <array>
+// CHECK-NEXT:       <array>
+// CHECK-NEXT:        <dict>
+// CHECK-NEXT:         <key>line</key><integer>24</integer>
+// CHECK-NEXT:         <key>col</key><integer>3</integer>
+// CHECK-NEXT:         <key>file</key><integer>0</integer>
+// CHECK-NEXT:        </dict>
+// CHECK-NEXT:        <dict>
+// CHECK-NEXT:         <key>line</key><integer>24</integer>
+// CHECK-NEXT:         <key>col</key><integer>7</integer>
+// CHECK-NEXT:         <key>file</key><integer>0</integer>
+// CHECK-NEXT:        </dict>
+// CHECK-NEXT:       </array>
+// CHECK-NEXT:     </array>
+// CHECK-NEXT:     <key>depth</key><integer>0</integer>
+// CHECK-NEXT:     <key>extended_message</key>
+// CHECK-NEXT:     <string>&apos;p&apos; initialized to 0</string>
+// CHECK-NEXT:     <key>message</key>
+// CHECK-NEXT:     <string>&apos;p&apos; initialized to 0</string>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>kind</key><string>control</string>
+// CHECK-NEXT:     <key>edges</key>
+// CHECK-NEXT:      <array>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>start</key>
+// CHECK-NEXT:         <array>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>24</integer>
+// CHECK-NEXT:           <key>col</key><integer>3</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>24</integer>
+// CHECK-NEXT:           <key>col</key><integer>5</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:         </array>
+// CHECK-NEXT:        <key>end</key>
+// CHECK-NEXT:         <array>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>25</integer>
+// CHECK-NEXT:           <key>col</key><integer>3</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>25</integer>
+// CHECK-NEXT:           <key>col</key><integer>4</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:         </array>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:      </array>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>kind</key><string>control</string>
+// CHECK-NEXT:     <key>edges</key>
+// CHECK-NEXT:      <array>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>start</key>
+// CHECK-NEXT:         <array>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>25</integer>
+// CHECK-NEXT:           <key>col</key><integer>3</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>25</integer>
+// CHECK-NEXT:           <key>col</key><integer>4</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:         </array>
+// CHECK-NEXT:        <key>end</key>
+// CHECK-NEXT:         <array>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>31</integer>
+// CHECK-NEXT:           <key>col</key><integer>3</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>31</integer>
+// CHECK-NEXT:           <key>col</key><integer>4</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:         </array>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:      </array>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>kind</key><string>control</string>
+// CHECK-NEXT:     <key>edges</key>
+// CHECK-NEXT:      <array>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>start</key>
+// CHECK-NEXT:         <array>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>31</integer>
+// CHECK-NEXT:           <key>col</key><integer>3</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>31</integer>
+// CHECK-NEXT:           <key>col</key><integer>4</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:         </array>
+// CHECK-NEXT:        <key>end</key>
+// CHECK-NEXT:         <array>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>31</integer>
+// CHECK-NEXT:           <key>col</key><integer>7</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>31</integer>
+// CHECK-NEXT:           <key>col</key><integer>24</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:         </array>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:      </array>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>kind</key><string>control</string>
+// CHECK-NEXT:     <key>edges</key>
+// CHECK-NEXT:      <array>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>start</key>
+// CHECK-NEXT:         <array>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>31</integer>
+// CHECK-NEXT:           <key>col</key><integer>7</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>31</integer>
+// CHECK-NEXT:           <key>col</key><integer>24</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:         </array>
+// CHECK-NEXT:        <key>end</key>
+// CHECK-NEXT:         <array>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>35</integer>
+// CHECK-NEXT:           <key>col</key><integer>15</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:          <dict>
+// CHECK-NEXT:           <key>line</key><integer>35</integer>
+// CHECK-NEXT:           <key>col</key><integer>15</integer>
+// CHECK-NEXT:           <key>file</key><integer>0</integer>
+// CHECK-NEXT:          </dict>
+// CHECK-NEXT:         </array>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:      </array>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>kind</key><string>event</string>
+// CHECK-NEXT:     <key>location</key>
+// CHECK-NEXT:     <dict>
+// CHECK-NEXT:      <key>line</key><integer>35</integer>
+// CHECK-NEXT:      <key>col</key><integer>15</integer>
+// CHECK-NEXT:      <key>file</key><integer>0</integer>
+// CHECK-NEXT:     </dict>
+// CHECK-NEXT:     <key>ranges</key>
+// CHECK-NEXT:     <array>
+// CHECK-NEXT:       <array>
+// CHECK-NEXT:        <dict>
+// CHECK-NEXT:         <key>line</key><integer>35</integer>
+// CHECK-NEXT:         <key>col</key><integer>13</integer>
+// CHECK-NEXT:         <key>file</key><integer>0</integer>
+// CHECK-NEXT:        </dict>
+// CHECK-NEXT:        <dict>
+// CHECK-NEXT:         <key>line</key><integer>35</integer>
+// CHECK-NEXT:         <key>col</key><integer>17</integer>
+// CHECK-NEXT:         <key>file</key><integer>0</integer>
+// CHECK-NEXT:        </dict>
+// CHECK-NEXT:       </array>
+// CHECK-NEXT:     </array>
+// CHECK-NEXT:     <key>depth</key><integer>0</integer>
+// CHECK-NEXT:     <key>extended_message</key>
+// CHECK-NEXT:     <string>Division by zero</string>
+// CHECK-NEXT:     <key>message</key>
+// CHECK-NEXT:     <string>Division by zero</string>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:   </array>
+// CHECK-NEXT:   <key>description</key><string>Division by zero</string>
+// CHECK-NEXT:   <key>category</key><string>Logic error</string>
+// CHECK-NEXT:   <key>type</key><string>Division by zero</string>
+// CHECK-NEXT:  <key>issue_context_kind</key><string>function</string>
+// CHECK-NEXT:  <key>issue_context</key><string>main</string>
+// CHECK-NEXT:  <key>issue_hash</key><string>15</string>
+// CHECK-NEXT:  <key>location</key>
+// CHECK-NEXT:  <dict>
+// CHECK-NEXT:   <key>line</key><integer>35</integer>
+// CHECK-NEXT:   <key>col</key><integer>15</integer>
+// CHECK-NEXT:   <key>file</key><integer>0</integer>
+// CHECK-NEXT:  </dict>
+// CHECK-NEXT:  </dict>
+// CHECK-NEXT: </array>
\ No newline at end of file