blob: a96fada4240bd036ad0e04babfcbc7359e95542d [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTTypeTraits.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/AST/Stmt.h"
#include "clang/Driver/Options.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/TextDiagnostic.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Support/CommandLine.h"
// Using clang without importing namespaces is damn near impossible.
using namespace llvm; // NOLINT
using namespace clang::ast_matchers; // NOLINT
using llvm::opt::OptTable;
using clang::ast_type_traits::DynTypedNode;
using clang::driver::createDriverOptTable;
using clang::driver::options::OPT_ast_dump;
using clang::tooling::CommonOptionsParser;
using clang::tooling::CommandLineArguments;
using clang::tooling::ClangTool;
using clang::tooling::newFrontendActionFactory;
using clang::ASTContext;
using clang::CharSourceRange;
using clang::ClassTemplateSpecializationDecl;
using clang::Decl;
using clang::DiagnosticsEngine;
using clang::Stmt;
using clang::FixItHint;
using clang::SourceLocation;
using clang::SourceRange;
using clang::Stmt;
using clang::TextDiagnostic;
using std::string;
static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
static cl::extrahelp MoreHelp(
"\tFor example, to run kudu-lint on all files in a subtree of the\n"
"\tsource tree, use:\n"
"\n"
"\t find path/in/subtree -name '*.cc'|xargs kudu-lint\n"
"\n"
"\tor using a specific build path:\n"
"\n"
"\t find path/in/subtree -name '*.cc'|xargs kudu-lint -p build/path\n"
"\n"
"\tNote, that path/in/subtree and current directory should follow the\n"
"\trules described above.\n"
"\n"
"\t Sometimes kudu-lint can't figure out the proper line number for an\n"
"\t error, and reports it inside some standard library. the -ast-dump\n"
"\t option can be useful for these circumstances.\n"
"\n"
);
// Command line flags.
static OwningPtr<OptTable> gOptions(createDriverOptTable());
static cl::list<std::string> gArgsAfter(
"extra-arg",
cl::desc("Additional argument to append to the compiler command line"));
static cl::list<std::string> gArgsBefore(
"extra-arg-before",
cl::desc("Additional argument to prepend to the compiler command line"));
static cl::opt<bool> gASTDump("ast-dump",
cl::desc(gOptions->getOptionHelpText(OPT_ast_dump)));
namespace {
// Callback for unused statuses. Simply reports the error at the point where the
// expression was found.
template<class NODETYPE>
class ErrorPrinter : public MatchFinder::MatchCallback {
public:
ErrorPrinter(const std::string& error_msg,
const std::string& bound_name,
bool skip_system_headers)
: error_msg_(error_msg),
bound_name_(bound_name),
skip_system_headers_(skip_system_headers) {
}
virtual void run(const MatchFinder::MatchResult& result) {
const NODETYPE* node;
if ((node = result.Nodes.getNodeAs<NODETYPE>(bound_name_))) {
SourceRange r = node->getSourceRange();
if (skip_system_headers_ && result.SourceManager->isInSystemHeader(r.getBegin())) {
return;
}
if (gASTDump) {
node->dump();
}
if (r.isValid()) {
TextDiagnostic td(llvm::outs(), result.Context->getLangOpts(),
&result.Context->getDiagnostics().getDiagnosticOptions());
td.emitDiagnostic(r.getBegin(), DiagnosticsEngine::Error,
error_msg_,
ArrayRef<CharSourceRange>(CharSourceRange::getTokenRange(r)),
ArrayRef<FixItHint>(), result.SourceManager);
}
SourceLocation instantiation_point;
if (FindInstantiationPoint(result, node, &instantiation_point)) {
TextDiagnostic td(llvm::outs(), result.Context->getLangOpts(),
&result.Context->getDiagnostics().getDiagnosticOptions());
td.emitDiagnostic(instantiation_point, DiagnosticsEngine::Note,
"previous error instantiated at",
ArrayRef<CharSourceRange>(),
ArrayRef<FixItHint>(), result.SourceManager);
}
} else {
llvm_unreachable("bound node missing");
}
}
private:
bool GetParent(ASTContext* ctx, const DynTypedNode& node, DynTypedNode* parent) {
ASTContext::ParentVector parents = ctx->getParents(node);
if (parents.empty()) return false;
assert(parents.size() == 1);
*parent = parents[0];
return true;
}
// If the AST node 'node' has an ancestor which is a template instantiation,
// fill the source location of that instantiation into 'loc'. Unfortunately,
// Clang doesn't retain enough information in the AST nodes to recurse here --
// so in many cases this is useless, since the instantiation point will simply
// be inside another instantiated template.
bool FindInstantiationPoint(const MatchFinder::MatchResult& result,
const NODETYPE* node,
SourceLocation* loc) {
DynTypedNode dyn_node = DynTypedNode::create<NODETYPE>(*node);
// Recurse up the tree.
while (true) {
const ClassTemplateSpecializationDecl* D =
dyn_node.get<ClassTemplateSpecializationDecl>();
if (D) {
*loc = D->getPointOfInstantiation();
return true;
}
// TODO: there are probably other types of specializations to handle, but this is the only
// one seen so far.
DynTypedNode parent;
if (!GetParent(result.Context, dyn_node, &parent)) {
return false;
}
dyn_node = parent;
}
}
string error_msg_;
string bound_name_;
bool skip_system_headers_;
};
// Inserts arguments before or after the usual command line arguments.
class InsertAdjuster: public clang::tooling::ArgumentsAdjuster {
public:
enum Position { BEGIN, END };
InsertAdjuster(const CommandLineArguments &extra, Position pos)
: extra_(extra), pos_(pos) {
}
InsertAdjuster(const char *extra_, Position pos)
: extra_(1, std::string(extra_)), pos_(pos) {
}
virtual CommandLineArguments Adjust(const CommandLineArguments &Args) LLVM_OVERRIDE {
CommandLineArguments ret(Args);
CommandLineArguments::iterator I;
if (pos_ == END) {
I = ret.end();
} else {
I = ret.begin();
++I; // To leave the program name in place
}
ret.insert(I, extra_.begin(), extra_.end());
return ret;
}
private:
const CommandLineArguments extra_;
const Position pos_;
};
} // anonymous namespace
int main(int argc, const char **argv) {
CommonOptionsParser options_parser(argc, argv);
ClangTool Tool(options_parser.getCompilations(),
options_parser.getSourcePathList());
if (gArgsAfter.size() > 0) {
Tool.appendArgumentsAdjuster(new InsertAdjuster(gArgsAfter,
InsertAdjuster::END));
}
if (gArgsBefore.size() > 0) {
Tool.appendArgumentsAdjuster(new InsertAdjuster(gArgsBefore,
InsertAdjuster::BEGIN));
}
// Match expressions of type 'Status' which are parented by a compound statement.
// This implies that the expression is being thrown away, rather than assigned
// to some variable or function call.
//
// For more information on AST matchers, refer to:
// http://clang.llvm.org/docs/LibASTMatchersReference.html
StatementMatcher ignored_status_matcher =
expr(hasType(recordDecl(hasName("Status"))),
hasParent(compoundStmt())).bind("expr");
ErrorPrinter<Stmt> ignored_status_printer("Unused status result", "expr", false);
// Match class members which are reference-typed. This is confusing since they
// tend to "look like" copied values, but in fact often reference external
// entities passed in in the constructor.
DeclarationMatcher ref_member_matcher =
fieldDecl(hasType(referenceType()))
.bind("decl");
ErrorPrinter<Decl> ref_member_printer("Reference-typed member", "decl", true);
// Disallow calls to sleep, usleep, and nanosleep.
// SleepFor(MonoDelta) should be used instead, as it is not prone to
// unit conversion errors, and also ignores EINTR so will safely sleep
// at least the requested duration.
StatementMatcher sleep_matcher =
callExpr(callee(namedDecl(matchesName("(nano|u)?sleep")))).bind("sleep_expr");
ErrorPrinter<Stmt> sleep_printer("sleep, usleep or nanosleep call", "sleep_expr", true);
MatchFinder finder;
finder.addMatcher(ignored_status_matcher, &ignored_status_printer);
finder.addMatcher(ref_member_matcher, &ref_member_printer);
finder.addMatcher(sleep_matcher, &sleep_printer);
return Tool.run(newFrontendActionFactory(&finder));
}