summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/nullptr.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--compilerplugins/clang/nullptr.cxx431
1 files changed, 431 insertions, 0 deletions
diff --git a/compilerplugins/clang/nullptr.cxx b/compilerplugins/clang/nullptr.cxx
new file mode 100644
index 000000000..dcacb1bd2
--- /dev/null
+++ b/compilerplugins/clang/nullptr.cxx
@@ -0,0 +1,431 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cassert>
+#include <cstdlib>
+#include <limits>
+#include <set>
+
+#include "check.hxx"
+#include "compat.hxx"
+#include "plugin.hxx"
+
+namespace {
+
+char const * kindName(Expr::NullPointerConstantKind kind) {
+ switch (kind) {
+ case Expr::NPCK_ZeroExpression:
+ return "ZeroExpression";
+ case Expr::NPCK_ZeroLiteral:
+ return "ZeroLiteral";
+ case Expr::NPCK_CXX11_nullptr:
+ return "CXX11_nullptr";
+ case Expr::NPCK_GNUNull:
+ return "GNUNull";
+ case Expr::NPCK_NotNull:
+ break; // cannot happen
+ }
+ llvm_unreachable("unknown null pointer kind");
+}
+
+bool isAnyKindOfPointerType(QualType type) {
+ return type->isAnyPointerType() || type->isFunctionPointerType()
+ || type->isMemberPointerType();
+}
+
+bool isNullPointerCast(CastExpr const * expr) {
+ switch (expr->getCastKind()) {
+ case CK_NullToPointer:
+ case CK_NullToMemberPointer:
+ return true;
+ default:
+ return false;
+ }
+}
+
+class Nullptr:
+ public loplugin::FilteringRewritePlugin<Nullptr>
+{
+public:
+ explicit Nullptr(loplugin::InstantiationData const & data):
+ FilteringRewritePlugin(data) {}
+
+ void run() override
+ { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitImplicitCastExpr(CastExpr const * expr);
+
+ bool VisitGNUNullExpr(GNUNullExpr const * expr);
+
+ bool VisitBinaryOperator(BinaryOperator const * expr);
+
+ bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr const * expr);
+
+ bool VisitParmVarDecl(ParmVarDecl const * decl);
+
+ bool TraverseConstructorInitializer(CXXCtorInitializer * init);
+
+ bool TraverseLinkageSpecDecl(LinkageSpecDecl * decl);
+
+ bool TraverseInitListExpr(InitListExpr * expr, DataRecursionQueue * queue = nullptr);
+
+ bool shouldVisitTemplateInstantiations() const { return true; }
+
+private:
+ bool isInLokIncludeFile(SourceLocation spellingLocation) const;
+
+ bool isFromCIncludeFile(SourceLocation spellingLocation) const;
+
+ bool isSharedCAndCppCode(SourceLocation location) const;
+
+ void visitCXXCtorInitializer(CXXCtorInitializer const * init);
+
+ void handleZero(Expr const * expr);
+
+ void handleNull(
+ Expr const * expr, char const * castKind,
+ Expr::NullPointerConstantKind nullPointerKind);
+
+ void rewriteOrWarn(
+ Expr const * expr, char const * castKind,
+ Expr::NullPointerConstantKind nullPointerKind,
+ char const * replacement);
+
+ std::set<Expr const *> gnuNulls_;
+ unsigned int externCContexts_ = 0;
+};
+
+bool Nullptr::VisitImplicitCastExpr(CastExpr const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (!isNullPointerCast(expr)) {
+ return true;
+ }
+ Expr::NullPointerConstantKind k = expr->isNullPointerConstant(
+ compiler.getASTContext(), Expr::NPC_ValueDependentIsNotNull);
+ switch (k) {
+ case Expr::NPCK_NotNull:
+ k = expr->isNullPointerConstant(
+ compiler.getASTContext(), Expr::NPC_ValueDependentIsNull);
+ switch (k) {
+ case Expr::NPCK_NotNull:
+ break;
+ case Expr::NPCK_ZeroExpression:
+ case Expr::NPCK_ZeroLiteral:
+ report(
+ DiagnosticsEngine::Warning,
+ "suspicious ValueDependentIsNull %0", expr->getBeginLoc())
+ << kindName(k) << expr->getSourceRange();
+ break;
+ default:
+ assert(false); // cannot happen
+ }
+ break;
+ case Expr::NPCK_CXX11_nullptr:
+ break;
+ default:
+ if (loplugin::TypeCheck(expr->getType()).Typedef("locale_t")
+ .GlobalNamespace())
+ {
+ break; // POSIX locale_t is left unspecified
+ }
+ handleNull(expr->getSubExpr(), expr->getCastKindName(), k);
+ break;
+ }
+ return true;
+}
+
+bool Nullptr::VisitGNUNullExpr(GNUNullExpr const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ handleNull(expr, nullptr, Expr::NPCK_GNUNull);
+ return true;
+}
+
+bool Nullptr::VisitBinaryOperator(BinaryOperator const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ Expr const * e;
+ switch (expr->getOpcode()) {
+ case BO_EQ:
+ case BO_NE:
+ if (isAnyKindOfPointerType(expr->getRHS()->getType())) {
+ e = expr->getLHS();
+ break;
+ }
+ // fall through
+ case BO_Assign:
+ if (isAnyKindOfPointerType(expr->getLHS()->getType())) {
+ e = expr->getRHS();
+ break;
+ }
+ // fall through
+ default:
+ return true;
+ }
+ handleZero(e);
+ return true;
+}
+
+bool Nullptr::VisitCXXOperatorCallExpr(CXXOperatorCallExpr const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ Expr const * e;
+ switch (expr->getOperator()) {
+ case OO_EqualEqual:
+ case OO_ExclaimEqual:
+ if (isAnyKindOfPointerType(expr->getArg(1)->getType())) {
+ e = expr->getArg(0);
+ break;
+ }
+ // fall through
+ case OO_Equal:
+ if (isAnyKindOfPointerType(expr->getArg(0)->getType())) {
+ e = expr->getArg(1);
+ break;
+ }
+ // fall through
+ default:
+ return true;
+ }
+ handleZero(e);
+ return true;
+}
+
+bool Nullptr::VisitParmVarDecl(ParmVarDecl const * decl) {
+ if (ignoreLocation(decl)) {
+ return true;
+ }
+ if (!isAnyKindOfPointerType(decl->getType())) {
+ return true;
+ }
+ if (decl->hasUninstantiatedDefaultArg()) {
+ return true; //TODO
+ }
+ auto e = decl->getDefaultArg();
+ if (e == nullptr) {
+ return true;
+ }
+ handleZero(e);
+ return true;
+}
+
+bool Nullptr::TraverseConstructorInitializer(CXXCtorInitializer * init) {
+ visitCXXCtorInitializer(init);
+ return RecursiveASTVisitor::TraverseConstructorInitializer(init);
+}
+
+bool Nullptr::TraverseLinkageSpecDecl(LinkageSpecDecl * decl) {
+ assert(externCContexts_ != std::numeric_limits<unsigned int>::max()); //TODO
+ ++externCContexts_;
+ bool ret = RecursiveASTVisitor::TraverseLinkageSpecDecl(decl);
+ assert(externCContexts_ != 0);
+ --externCContexts_;
+ return ret;
+}
+
+bool Nullptr::TraverseInitListExpr(InitListExpr * expr, DataRecursionQueue * queue) {
+ return WalkUpFromInitListExpr(expr)
+ && TraverseSynOrSemInitListExpr(
+ expr->isSemanticForm() ? expr : expr->getSemanticForm(), queue);
+}
+
+bool Nullptr::isInLokIncludeFile(SourceLocation spellingLocation) const {
+ return loplugin::hasPathnamePrefix(
+ getFilenameOfLocation(spellingLocation),
+ SRCDIR "/include/LibreOfficeKit/");
+}
+
+bool Nullptr::isFromCIncludeFile(SourceLocation spellingLocation) const {
+ return !compiler.getSourceManager().isInMainFile(spellingLocation)
+ && (StringRef(
+ compiler.getSourceManager().getPresumedLoc(spellingLocation)
+ .getFilename())
+ .endswith(".h"));
+}
+
+bool Nullptr::isSharedCAndCppCode(SourceLocation location) const {
+ // Assume that code is intended to be shared between C and C++ if it comes
+ // from an include file ending in .h, and is either in an extern "C" context
+ // or the body of a macro definition:
+ return
+ isFromCIncludeFile(compiler.getSourceManager().getSpellingLoc(location))
+ && (externCContexts_ != 0
+ || compiler.getSourceManager().isMacroBodyExpansion(location));
+}
+
+void Nullptr::visitCXXCtorInitializer(CXXCtorInitializer const * init) {
+ if (!init->isWritten()) {
+ return;
+ }
+ auto e = init->getInit();
+ if (ignoreLocation(e)) {
+ return;
+ }
+ auto d = init->getAnyMember();
+ if (d == nullptr || !isAnyKindOfPointerType(d->getType())) {
+ return;
+ }
+ if (auto e2 = dyn_cast<ParenListExpr>(e)) {
+ if (e2->getNumExprs() != 1) {
+ return;
+ }
+ e = e2->getExpr(0);
+ } else if (auto e2 = dyn_cast<InitListExpr>(e)) {
+ if (e2->getNumInits() != 1) {
+ return;
+ }
+ e = e2->getInit(0);
+ }
+ handleZero(e);
+}
+
+void Nullptr::handleZero(Expr const * expr) {
+ //TODO: detect NPCK_ZeroExpression where appropriate
+ // Filter out ImplicitCastExpr that will be handled by
+ // VisitImplicitCastExpr:
+ if (auto ice = dyn_cast<ImplicitCastExpr>(expr)) {
+ if (isNullPointerCast(ice)) {
+ return;
+ }
+ }
+ auto const lit = dyn_cast<IntegerLiteral>(expr->IgnoreParenImpCasts());
+ if (lit != nullptr && !lit->getValue().getBoolValue()) {
+ handleNull(expr, nullptr, Expr::NPCK_ZeroLiteral);
+ }
+}
+
+void Nullptr::handleNull(
+ Expr const * expr, char const * castKind,
+ Expr::NullPointerConstantKind nullPointerKind)
+{
+ auto e = expr;
+ SourceLocation loc;
+ for (;;) {
+ e = e->IgnoreImpCasts();
+ loc = e->getBeginLoc();
+ while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
+ loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
+ }
+ if (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
+ if (Lexer::getImmediateMacroName(
+ loc, compiler.getSourceManager(), compiler.getLangOpts())
+ == "NULL")
+ {
+ if (!compiler.getLangOpts().CPlusPlus) {
+ //TODO: if !castKind, warn if NULL is passed into fn call
+ // ellipsis, cast to void*
+ return;
+ }
+ loc = compat::getImmediateExpansionRange(compiler.getSourceManager(), loc).first;
+ if (ignoreLocation(
+ compiler.getSourceManager().getSpellingLoc(loc)))
+ {
+ return;
+ }
+ if (isInUnoIncludeFile(
+ compiler.getSourceManager().getSpellingLoc(loc))
+ || isInLokIncludeFile(
+ compiler.getSourceManager().getSpellingLoc(loc))
+ || isSharedCAndCppCode(loc))
+ {
+ //TODO: if !castKind, warn if NULL is passed into fn call
+ // ellipsis, cast to void*
+ return;
+ }
+ } else if (ignoreLocation(
+ compiler.getSourceManager().getSpellingLoc(loc)))
+ {
+ return;
+ }
+ }
+ ParenExpr const * pe = dyn_cast<ParenExpr>(e);
+ if (pe == nullptr) {
+ break;
+ }
+ e = pe->getSubExpr();
+ }
+ if (nullPointerKind == Expr::NPCK_GNUNull) {
+ if (castKind == nullptr) {
+ if (gnuNulls_.erase(expr) == 1) {
+ return;
+ }
+ } else {
+ auto const ok = gnuNulls_.insert(expr).second;
+ assert(ok); (void) ok;
+ }
+ }
+ auto const asMacro = !compiler.getLangOpts().CPlusPlus
+ || isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(loc))
+ || isInLokIncludeFile(compiler.getSourceManager().getSpellingLoc(loc))
+ || isSharedCAndCppCode(loc);
+ assert(!asMacro || nullPointerKind != Expr::NPCK_GNUNull);
+ rewriteOrWarn(e, castKind, nullPointerKind, asMacro ? "NULL" : "nullptr");
+}
+
+void Nullptr::rewriteOrWarn(
+ Expr const * expr, char const * castKind,
+ Expr::NullPointerConstantKind nullPointerKind, char const * replacement)
+{
+ if (rewriter != nullptr) {
+ SourceLocation locStart(expr->getBeginLoc());
+ while (compiler.getSourceManager().isMacroArgExpansion(locStart)) {
+ locStart = compiler.getSourceManager()
+ .getImmediateMacroCallerLoc(locStart);
+ }
+ if (compiler.getLangOpts().CPlusPlus
+ && compiler.getSourceManager().isMacroBodyExpansion(locStart)
+ && (Lexer::getImmediateMacroName(
+ locStart, compiler.getSourceManager(),
+ compiler.getLangOpts())
+ == "NULL"))
+ {
+ locStart = compat::getImmediateExpansionRange(compiler.getSourceManager(), locStart)
+ .first;
+ }
+ SourceLocation locEnd(expr->getEndLoc());
+ while (compiler.getSourceManager().isMacroArgExpansion(locEnd)) {
+ locEnd = compiler.getSourceManager()
+ .getImmediateMacroCallerLoc(locEnd);
+ }
+ if (compiler.getLangOpts().CPlusPlus
+ && compiler.getSourceManager().isMacroBodyExpansion(locEnd)
+ && (Lexer::getImmediateMacroName(
+ locEnd, compiler.getSourceManager(),
+ compiler.getLangOpts())
+ == "NULL"))
+ {
+ locEnd = compat::getImmediateExpansionRange(compiler.getSourceManager(), locEnd).first;
+ }
+ if (replaceText(SourceRange(compiler.getSourceManager().getSpellingLoc(locStart), compiler.getSourceManager().getSpellingLoc(locEnd)), replacement)) {
+ return;
+ }
+ }
+ if (castKind == nullptr) {
+ report(DiagnosticsEngine::Warning, "%0 -> %1", expr->getBeginLoc())
+ << kindName(nullPointerKind) << replacement
+ << expr->getSourceRange();
+ } else {
+ report(
+ DiagnosticsEngine::Warning, "%0 ValueDependentIsNotNull %1 -> %2",
+ expr->getBeginLoc())
+ << castKind << kindName(nullPointerKind) << replacement
+ << expr->getSourceRange();
+ }
+}
+
+loplugin::Plugin::Registration<Nullptr> X("nullptr", true);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */