Index: clang/include/clang/Basic/DiagnosticFrontendKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -174,6 +174,12 @@ def err_unknown_analyzer_checker : Error< "no analyzer checkers are associated with '%0'">; +def err_declarative_checker_yaml_parsing : Error< + "failed parsing declarative checker from YAML (read from file '%0')">; +def err_declarative_checker_file_not_found: Error< + "file '%0' with a checker description not found">; +def err_declarative_checker_misformed_matcher: Error< + "failed parsing matcher from file '%0': '%1'">; def note_suggest_disabling_all_checkers : Note< "use -analyzer-disable-all-checks to disable all static analyzer checkers">; Index: clang/include/clang/Basic/DiagnosticIDs.h =================================================================== --- clang/include/clang/Basic/DiagnosticIDs.h +++ clang/include/clang/Basic/DiagnosticIDs.h @@ -30,7 +30,7 @@ enum { DIAG_SIZE_COMMON = 300, DIAG_SIZE_DRIVER = 200, - DIAG_SIZE_FRONTEND = 100, + DIAG_SIZE_FRONTEND = 200, DIAG_SIZE_SERIALIZATION = 120, DIAG_SIZE_LEX = 400, DIAG_SIZE_PARSE = 500, Index: clang/include/clang/Driver/CC1Options.td =================================================================== --- clang/include/clang/Driver/CC1Options.td +++ clang/include/clang/Driver/CC1Options.td @@ -119,6 +119,9 @@ def analyzer_checker_EQ : Joined<["-"], "analyzer-checker=">, Alias; +def analyzer_load_checker : Separate<["-"], "analyzer-load-checker">, + HelpText<"Load a checker declaratively">; + def analyzer_disable_checker : Separate<["-"], "analyzer-disable-checker">, HelpText<"Choose analyzer checkers to disable">; def analyzer_disable_checker_EQ : Joined<["-"], "analyzer-disable-checker=">, Index: clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -130,6 +130,9 @@ /// Pair of checker name and enable/disable. std::vector> CheckersControlList; + + /// A vector of declarative checker filenames which should be loaded. + std::vector DeclarativeCheckerFileNames; /// A key-value table of use-specified configuration values. ConfigTable Config; Index: clang/include/clang/StaticAnalyzer/Core/CheckerRegistry.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/CheckerRegistry.h +++ clang/include/clang/StaticAnalyzer/Core/CheckerRegistry.h @@ -85,12 +85,12 @@ public: /// Initialization functions perform any necessary setup for a checker. /// They should include a call to CheckerManager::registerChecker. - using InitializationFunction = void (*)(CheckerManager &); + using InitializationFunction = std::function; struct CheckerInfo { InitializationFunction Initialize; - StringRef FullName; - StringRef Desc; + std::string FullName; + std::string Desc; CheckerInfo(InitializationFunction fn, StringRef name, StringRef desc) : Initialize(fn), FullName(name), Desc(desc) {} Index: clang/include/clang/StaticAnalyzer/Frontend/DeclarativeChecker.h =================================================================== --- /dev/null +++ clang/include/clang/StaticAnalyzer/Frontend/DeclarativeChecker.h @@ -0,0 +1,62 @@ +//= DeclarativeChecker.h - Classes for representing declarations *- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a DeclarativeChecker, which is defined by an ASTChecker +// string loaded from a user-provided file in YAML format. +// The user-provided file additionally specifies which warnings should be +// emitted on which bound nodes. +// +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_STATICANALYZER_DECLARATIVECHECKER_H +#define LLVM_CLANG_STATICANALYZER_DECLARATIVECHECKER_H + +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/ADT/Twine.h" + +using namespace clang::ast_matchers::dynamic; + +namespace clang { +namespace ento { + +class DeclarativeChecker : public Checker { + ast_matchers::internal::Matcher Matcher; + llvm::StringMap WarningsMap; + std::string CheckerCategory; + std::string CheckerName; + +public: + DeclarativeChecker(ast_matchers::internal::Matcher Matcher, + llvm::StringMap WarningsMap, + std::string CheckerCategory, + std::string CheckerName) : + Matcher(Matcher), + WarningsMap(WarningsMap), + CheckerCategory(CheckerCategory), + CheckerName(CheckerName) {} + + void checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const; + + static void registerCheckerFromFilename(llvm::StringRef Filename, + CheckerRegistry &Registry, + DiagnosticsEngine &Diags); +}; + +} // ento +} // clang + +#endif // LLVM_CLANG_STATICANALYZER_DECLARATIVECHECKER_H Index: clang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- clang/lib/Frontend/CompilerInvocation.cpp +++ clang/lib/Frontend/CompilerInvocation.cpp @@ -315,6 +315,15 @@ Opts.CheckersControlList.emplace_back(checker, enable); } + // Populate the list of filenames from which declarative checkers + // should be loaded. + for (const Arg *A : Args.filtered(OPT_analyzer_load_checker)) { + A->claim(); + // FIXME: supporting multiple checkers + StringRef checkerToLoad = A->getValue(); + Opts.DeclarativeCheckerFileNames.push_back(checkerToLoad); + } + // Go through the analyzer configuration options. for (const auto *A : Args.filtered(OPT_analyzer_config)) { A->claim(); Index: clang/lib/StaticAnalyzer/Core/CheckerRegistry.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/CheckerRegistry.cpp +++ clang/lib/StaticAnalyzer/Core/CheckerRegistry.cpp @@ -38,7 +38,7 @@ static bool isInPackage(const CheckerRegistry::CheckerInfo &checker, StringRef packageName) { // Does the checker's full name have the package as a prefix? - if (!checker.FullName.startswith(packageName)) + if (!StringRef(checker.FullName).startswith(packageName)) return false; // Is the package actually just the name of a specific checker? @@ -127,7 +127,7 @@ bool hasChecker = false; StringRef checkerName = config.getKey().substr(0, pos); for (const auto &checker : Checkers) { - if (checker.FullName.startswith(checkerName) && + if (StringRef(checker.FullName).startswith(checkerName) && (checker.FullName.size() == pos || checker.FullName[pos] == '.')) { hasChecker = true; break; Index: clang/lib/StaticAnalyzer/Frontend/CMakeLists.txt =================================================================== --- clang/lib/StaticAnalyzer/Frontend/CMakeLists.txt +++ clang/lib/StaticAnalyzer/Frontend/CMakeLists.txt @@ -7,12 +7,14 @@ add_clang_library(clangStaticAnalyzerFrontend AnalysisConsumer.cpp CheckerRegistration.cpp + DeclarativeChecker.cpp FrontendActions.cpp ModelConsumer.cpp ModelInjector.cpp LINK_LIBS clangAST + clangDynamicASTMatchers clangAnalysis clangBasic clangCrossTU Index: clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp =================================================================== --- clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp +++ clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp @@ -20,6 +20,7 @@ #include "clang/StaticAnalyzer/Core/CheckerOptInfo.h" #include "clang/StaticAnalyzer/Core/CheckerRegistry.h" #include "clang/StaticAnalyzer/Frontend/FrontendActions.h" +#include "clang/StaticAnalyzer/Frontend/DeclarativeChecker.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/Path.h" @@ -126,6 +127,10 @@ for (const auto &Fn : checkerRegistrationFns) Fn(allCheckers); + for (std::string &CheckerFile : opts.DeclarativeCheckerFileNames) + DeclarativeChecker::registerCheckerFromFilename(CheckerFile, allCheckers, + diags); + allCheckers.initializeManager(*checkerMgr, checkerOpts); allCheckers.validateCheckerOptions(opts, diags); checkerMgr->finishedCheckerRegistration(); Index: clang/lib/StaticAnalyzer/Frontend/DeclarativeChecker.cpp =================================================================== --- /dev/null +++ clang/lib/StaticAnalyzer/Frontend/DeclarativeChecker.cpp @@ -0,0 +1,184 @@ +//===- DeclarativeChecker.cpp ------------------------------------*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implementation for the DeclarativeChecker +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/ASTMatchers/Dynamic/Diagnostics.h" +#include "clang/StaticAnalyzer/Core/CheckerRegistry.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/StaticAnalyzer/Frontend/DeclarativeChecker.h" +#include "llvm/ObjectYAML/YAML.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" + +using namespace clang; +using namespace ento; +using namespace ast_matchers; +using namespace ast_matchers::dynamic; + +struct DeclarativeCheckerInfo { + + /// Name of the checker. + std::string Name; + + /// Description of the checker. + std::string Description; + + /// Category for the warning. + std::string Category; + + /// Declarative matcher used by the checker, + /// *including* the bindings. + std::string ASTMatcher; + + /// Mapping from bindings to produced warnings. + llvm::StringMap Warnings; +}; + + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits { + static void mapping(IO &Io, DeclarativeCheckerInfo &Obj) { + Io.mapRequired("name", Obj.Name); + Io.mapRequired("description", Obj.Description); + Io.mapRequired("category", Obj.Category); + Io.mapRequired("ast_matcher", Obj.ASTMatcher); + Io.mapRequired("warnings", Obj.Warnings); + } +}; + +template <> +struct CustomMappingTraits> { + static void inputOne(IO &io, StringRef Key, + llvm::StringMap &V) { + io.mapRequired(Key.str().c_str(), V[Key]); + } + + static void output(IO &io, llvm::StringMap &V) { + for (auto &P : V) + io.mapRequired(P.first().data(), P.second); + } +}; + +} // yaml +} // llvm + +static Optional +loadCheckerInfoFromFilename(llvm::StringRef Filename, + DiagnosticsEngine &Diags) { + llvm::ErrorOr> ExpectedMemoryBuffer = + llvm::MemoryBuffer::getFile(Filename); + // FIXME: should we show the error code? + std::error_code E = ExpectedMemoryBuffer.getError(); + if (!E) { + std::unique_ptr MemoryBuffer = + std::move(ExpectedMemoryBuffer.get()); + llvm::yaml::Input Yin(*MemoryBuffer); + DeclarativeCheckerInfo Benchmark; + Yin >> Benchmark; + if (Yin.error()) { + Diags.Report(diag::err_declarative_checker_yaml_parsing) + << Filename; + return None; + } + return Benchmark; + } else { + Diags.Report(diag::err_declarative_checker_file_not_found) << Filename; + return None; + } +} + +void DeclarativeChecker::registerCheckerFromFilename( + llvm::StringRef Filename, CheckerRegistry &Registry, + DiagnosticsEngine &Diags) { + llvm::Optional Info = + loadCheckerInfoFromFilename(Filename, Diags); + + if (!Info) + return; + + // Force the matcher to be be a decl matcher. + // This works, as everything is wrapped in a top-level decl. + std::string ASTMatcher = "decl(forEachDescendant(" + Info->ASTMatcher + "))"; + + Diagnostics ParserDiag; + Optional DynMatcher = Parser::parseMatcherExpression( + ASTMatcher, /*Sema=*/nullptr, /*NamedValueMap=*/nullptr, &ParserDiag); + + if (!DynMatcher) { + Diags.Report(diag::err_declarative_checker_misformed_matcher) + << Filename << ParserDiag.toStringFull(); + return; + } + + ast_matchers::internal::Matcher DeclMatcher = + DynMatcher->convertTo(); + + // Capture by value is required, as ownership is transferred to the callback. + DeclarativeCheckerInfo CInfo = *Info; + Registry.addChecker( + [DeclMatcher, CInfo](CheckerManager &Mgr) { + return Mgr.registerChecker( + DeclMatcher, CInfo.Warnings, CInfo.Category, CInfo.Name); + }, + CInfo.Name, CInfo.Description); +} + +template +static void emitReport(AnalysisDeclContext *ADC, BugReporter &BR, + const DeclarativeChecker *Checker, + const StringRef WarningTxt, + const StringRef CheckerCategory, + T Node) { + + PathDiagnosticLocation Location = + PathDiagnosticLocation::createBegin(Node, BR.getSourceManager(), ADC); + + clang::SourceRange Range = Node->getSourceRange(); + + BR.EmitBasicReport(ADC->getDecl(), Checker, WarningTxt, CheckerCategory, + WarningTxt, Location, Range); +} + +void DeclarativeChecker::checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const { + AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); + auto Matches = match(Matcher, *D, AM.getASTContext()); + + for (BoundNodes Match : Matches) { + for (auto &P : WarningsMap) { + StringRef BoundName = P.first(); + StringRef WarningTxt = P.second; + + if (const Stmt *StmtNode = Match.getNodeAs(BoundName)) { + emitReport(ADC, BR, this, WarningTxt, CheckerCategory, + StmtNode); + + } else if (const Decl *DeclNode = Match.getNodeAs(BoundName)) { + emitReport(ADC, BR, this, WarningTxt, CheckerCategory, + DeclNode); + + } else { + + // TODO: supporting matching types/etc. + continue; + } + + } + } +} Index: clang/test/Analysis/declarative/failed_matcher.c =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/failed_matcher.c @@ -0,0 +1,5 @@ +// RUN: not %clang_analyze_cc1 -analyze -analyzer-load-checker %S/failed_matcher.yaml -analyzer-checker=mypackage.mychecker %s 2>&1 | FileCheck %s + +void foo() {} + +// CHECK: failed parsing matcher Index: clang/test/Analysis/declarative/failed_matcher.yaml =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/failed_matcher.yaml @@ -0,0 +1,11 @@ +name: + mypackage.mychecker +category: + core +description: + descr +ast_matcher: + (((()))) +warnings: + binding: "Variable declaration found" + Index: clang/test/Analysis/declarative/failed_yaml_parsing.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/failed_yaml_parsing.cpp @@ -0,0 +1,6 @@ +// RUN: not %clang_analyze_cc1 -analyze -analyzer-load-checker %S/misformed.yaml -analyzer-checker=mypackage.mychecker %s 2>&1 | FileCheck %s + +void foo(){} + +// CHECK: error: missing required key 'name' +// CHECK: failed parsing declarative checker from YAML Index: clang/test/Analysis/declarative/load_simple_matcher.c =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/load_simple_matcher.c @@ -0,0 +1,14 @@ +// RUN: %clang_analyze_cc1 -analyze -analyzer-load-checker %S/matcher.yaml -analyzer-checker=mypackage.mychecker %s + +int bar() { + int z = 120; // expected-warning{{Variable declaration found}} + return z; +} + +int foo() { + int a = 0; // expected-warning{{Variable declaration found}} + int b = 1; // expected-warning{{Variable declaration found}} + int c = 2; // expected-warning{{Variable declaration found}} + return a + b + c; +} + Index: clang/test/Analysis/declarative/matcher.yaml =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/matcher.yaml @@ -0,0 +1,11 @@ +name: + mypackage.mychecker +category: + core +description: + descr +ast_matcher: + varDecl().bind("binding") +warnings: + binding: "Variable declaration found" + Index: clang/test/Analysis/declarative/matcher_multiple_bindings.yaml =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/matcher_multiple_bindings.yaml @@ -0,0 +1,12 @@ +name: + mypackage.mychecker +category: + core +description: + descr +ast_matcher: + anyOf(varDecl(hasInitializer(integerLiteral(0)).bind("zero_init"), varDecl(hasInitializer(integerLiteral(1)).bind("one_init"))) +warnings: + zero_init: "Variable initialized to zero" + one_init: "Variable initialized to one" + Index: clang/test/Analysis/declarative/misformed.yaml =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/misformed.yaml @@ -0,0 +1 @@ +fail: fail Index: clang/test/Analysis/declarative/nonexistent_file.c =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/nonexistent_file.c @@ -0,0 +1,5 @@ +// RUN: not %clang_analyze_cc1 -analyze -analyzer-load-checker %S/nonexistentfile.yaml -analyzer-checker=mypackage.mychecker %s 2>&1 | FileCheck %s + +void foo() {} + +// CHECK: file '{{.*}}nonexistentfile.yaml' with a checker description not found Index: clang/test/Analysis/declarative/stmt_matcher.yaml =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/stmt_matcher.yaml @@ -0,0 +1,11 @@ +name: + mypackage.stmt_matcher +category: + core +description: + descr +ast_matcher: + returnStmt().bind("binding") +warnings: + binding: "Return statement" + Index: clang/test/Analysis/declarative/use_matcher_multiple_bindings.c =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/use_matcher_multiple_bindings.c @@ -0,0 +1,13 @@ +// RUN: %clang_analyze_cc1 -analyze -analyzer-load-checker %S/matcher.yaml -analyzer-checker=mypackage.mychecker %s + +int bar() { + int z = 0; // expected-warning{{Variable initialized to zero}} + return z; +} + +int foo() { + int a = 0; // expected-warning{{Variable initialized to zero}} + int b = 1; // expected-warning{{Variable initialized to one}} + return a + b; +} + Index: clang/test/Analysis/declarative/use_stmt_matcher.c =================================================================== --- /dev/null +++ clang/test/Analysis/declarative/use_stmt_matcher.c @@ -0,0 +1,9 @@ +// RUN: %clang_analyze_cc1 -analyze -analyzer-load-checker %S/stmt_matcher.yaml -analyzer-checker=mypackage.stmt_matcher %s + +int foo() { + int a = 0; + int b = 0; + int c = 0; + return a + b + c; // expected-warning{{Return statement}} +} +