blob: 0946fbe22ec5880d6e48ec99f33049cc1f892bfc [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// infer_tu_main infers nullability within a single translation unit.
//
// By default (-diagnostics=1) it shows findings as diagnostics.
// It can optionally (-protos=1) print the Inference proto.
//
// This is not the intended way to fully analyze a real codebase.
// e.g. it can't jointly inspect all callsites of a function (in different TUs).
#include <memory>
#include <string>
#include <utility>
#include "absl/log/check.h"
#include "nullability/inference/infer_tu.h"
#include "nullability/inference/inference.proto.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/Decl.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Index/USRGeneration.h"
#include "clang/Tooling/ArgumentsAdjusters.h"
#include "clang/Tooling/Execution.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
llvm::cl::OptionCategory Opts("infer_tu_main options");
llvm::cl::opt<bool> PrintProtos{
"protos",
llvm::cl::desc("Print the Inference protos"),
llvm::cl::init(false),
};
llvm::cl::opt<bool> Diagnostics{
"diagnostics",
llvm::cl::desc("Print inference results as diagnostics"),
llvm::cl::init(true),
};
llvm::cl::opt<bool> PrintEvidence{
"evidence",
llvm::cl::desc("Print sample evidence as notes (requires -diagnostics)"),
llvm::cl::init(true),
};
llvm::cl::opt<bool> IncludeTrivial{
"trivial",
llvm::cl::desc("Include trivial inferences (annotated, no conflicts)"),
llvm::cl::init(false),
};
namespace clang::tidy::nullability {
namespace {
// Walks the AST looking for declarations of symbols we inferred.
// When it finds them, prints the inference as diagnostics.
class DiagnosticPrinter : public RecursiveASTVisitor<DiagnosticPrinter> {
llvm::DenseMap<llvm::StringRef, const Inference *> InferenceByUSR;
DiagnosticsEngine &Diags;
unsigned DiagInferHere;
unsigned DiagSample;
void render(const Inference &I, SourceLocation Loc) {
for (const auto &Slot : I.slot_inference()) {
Diags.Report(Loc, DiagInferHere)
<< slotName(Slot.slot())
<< Inference::Nullability_Name(Slot.nullability());
if (PrintEvidence) {
for (const auto &Sample : Slot.sample_evidence()) {
if (SourceLocation Loc = parseLoc(Sample.location()); Loc.isValid())
Diags.Report(Loc, DiagSample) << Evidence::Kind_Name(Sample.kind());
}
}
}
}
std::string slotName(unsigned S) {
if (S == SLOT_RETURN_TYPE) return "return type";
return ("parameter " + llvm::Twine(S - SLOT_PARAM)).str();
}
// Terrible hack: parse "foo.cc:4:2" back into a SourceLocation.
SourceLocation parseLoc(llvm::StringRef LocStr) {
auto &SM = Diags.getSourceManager();
auto &FM = SM.getFileManager();
auto [Rest, ColStr] = llvm::StringRef(LocStr).rsplit(':');
auto [Name, LineStr] = Rest.rsplit(':');
auto File = FM.getOptionalFileRef(Name);
unsigned Line, Col;
if (!File || LineStr.getAsInteger(10, Line) || ColStr.getAsInteger(10, Col))
return SourceLocation();
return SM.translateFileLineCol(&File->getFileEntry(), Line, Col);
}
public:
DiagnosticPrinter(llvm::ArrayRef<Inference> All, DiagnosticsEngine &Diags)
: Diags(Diags) {
for (const auto &I : All) InferenceByUSR.try_emplace(I.symbol().usr(), &I);
DiagInferHere = Diags.getCustomDiagID(DiagnosticsEngine::Remark,
"would mark %0 as %1 here");
DiagSample = Diags.getCustomDiagID(DiagnosticsEngine::Note, "%0 here");
}
bool VisitDecl(const Decl *FD) {
llvm::SmallString<128> USR;
if (!index::generateUSRForDecl(FD, USR))
if (auto *I = InferenceByUSR.lookup(USR)) render(*I, FD->getLocation());
return true;
}
};
bool isTrivial(const Inference::SlotInference &I) {
if (I.conflict()) return false;
for (const auto &E : I.sample_evidence())
if (E.kind() == Evidence::ANNOTATED_NONNULL ||
E.kind() == Evidence::ANNOTATED_NULLABLE)
return true;
return false;
}
class Action : public SyntaxOnlyAction {
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &,
llvm::StringRef) override {
class Consumer : public ASTConsumer {
void HandleTranslationUnit(ASTContext &Ctx) override {
llvm::errs() << "Running inference...\n";
auto Results = inferTU(Ctx);
if (!IncludeTrivial)
llvm::erase_if(Results, [](Inference &I) {
llvm::erase_if(*I.mutable_slot_inference(), isTrivial);
return I.slot_inference_size() == 0;
});
if (PrintProtos)
for (const auto &I : Results) llvm::outs() << I.DebugString() << "\n";
if (Diagnostics)
DiagnosticPrinter(Results, Ctx.getDiagnostics()).TraverseAST(Ctx);
}
};
return std::make_unique<Consumer>();
}
};
} // namespace
} // namespace clang::tidy::nullability
int main(int argc, const char **argv) {
using namespace clang::tooling;
auto Exec = createExecutorFromCommandLineArgs(argc, argv, Opts);
QCHECK(Exec) << toString(Exec.takeError());
auto Err = (*Exec)->execute(
newFrontendActionFactory<clang::tidy::nullability::Action>(),
// Disable warnings, testcases are full of unused expressions etc.
getInsertArgumentAdjuster("-w", ArgumentInsertPosition::BEGIN));
QCHECK(!Err) << toString(std::move(Err));
}