summaryrefslogtreecommitdiff
path: root/compilerplugins
diff options
context:
space:
mode:
authorStephan Bergmann <sbergman@redhat.com>2014-02-25 10:48:55 +0100
committerStephan Bergmann <sbergman@redhat.com>2014-02-25 10:48:55 +0100
commitcc478960dceea17858ba88067dc3ecf07a07d291 (patch)
treeec2d0eb0c08b43a115503e92ae04474470759484 /compilerplugins
parent6f2774b209f0a52ad199f926cd56f581e73c79d5 (diff)
salbool: a rewriting plugin that flags most uses of sal_Bool
...that would not lead to silent changes of the code. That is, it does not warn about sal_Bool parameters of virtual functions, so that (not yet rewritten) overrides do not silently become overloads instead. The plugin is in store/ for now, because not all of the code has been cleaned up yet. Change-Id: I6e9b3847eb26c3090f375045188d38097052defe
Diffstat (limited to 'compilerplugins')
-rw-r--r--compilerplugins/clang/compat.hxx18
-rw-r--r--compilerplugins/clang/store/salbool.cxx566
2 files changed, 584 insertions, 0 deletions
diff --git a/compilerplugins/clang/compat.hxx b/compilerplugins/clang/compat.hxx
index b09a221c97d0..fa89eba0b2a7 100644
--- a/compilerplugins/clang/compat.hxx
+++ b/compilerplugins/clang/compat.hxx
@@ -14,6 +14,8 @@
#include <string>
#include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Diagnostic.h"
@@ -25,6 +27,22 @@
// Compatibility wrapper to abstract over (trivial) changes in the Clang API:
namespace compat {
+inline bool isExternCContext(clang::DeclContext const & ctxt) {
+#if (__clang_major__ == 3 && __clang_minor__ >= 4) || __clang_major__ > 3
+ return ctxt.isExternCContext();
+#else
+ for (clang::DeclContext const * c = &ctxt;
+ c->getDeclKind() != clang::Decl::TranslationUnit; c = c->getParent())
+ {
+ if (c->getDeclKind() == clang::Decl::LinkageSpec) {
+ return llvm::cast<clang::LinkageSpecDecl>(c)->getLanguage()
+ == clang::LinkageSpecDecl::lang_c;
+ }
+ }
+ return false;
+#endif
+}
+
inline clang::QualType getReturnType(clang::FunctionDecl const & decl) {
#if (__clang_major__ == 3 && __clang_minor__ >= 5) || __clang_major__ > 3
return decl.getReturnType();
diff --git a/compilerplugins/clang/store/salbool.cxx b/compilerplugins/clang/store/salbool.cxx
new file mode 100644
index 000000000000..955222751694
--- /dev/null
+++ b/compilerplugins/clang/store/salbool.cxx
@@ -0,0 +1,566 @@
+/* -*- 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 <algorithm>
+#include <cassert>
+#include <set>
+#include <string>
+
+#include "clang/AST/Attr.h"
+
+#include "compat.hxx"
+#include "plugin.hxx"
+
+namespace {
+
+bool isSalBool(QualType type) {
+ TypedefType const * t = type->getAs<TypedefType>();
+ return t != nullptr && t->getDecl()->getNameAsString() == "sal_Bool";
+}
+
+// It appears that, given a function declaration, there is no way to determine
+// the language linkage of the function's type, only of the function's name
+// (via FunctionDecl::isExternC); however, in a case like
+//
+// extern "C" { static void f(); }
+//
+// the function's name does not have C language linkage while the function's
+// type does (as clarified in C++11 [decl.link]); cf. <http://clang-developers.
+// 42468.n3.nabble.com/Language-linkage-of-function-type-tt4037248.html>
+// "Language linkage of function type":
+bool hasCLanguageLinkageType(FunctionDecl const * decl) {
+ assert(decl != nullptr);
+ if (decl->isExternC()) {
+ return true;
+ }
+#if (__clang_major__ == 3 && __clang_minor__ >= 3) || __clang_major__ > 3
+ if (decl->isInExternCContext()) {
+ return true;
+ }
+#else
+ if (decl->getCanonicalDecl()->getDeclContext()->isExternCContext()) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+bool canOverrideTemplateArgumentMember(CXXMethodDecl const * decl) {
+ CXXRecordDecl const * r = dyn_cast<CXXRecordDecl>(decl->getDeclContext());
+ assert(r != nullptr);
+ for (auto b = r->bases_begin(); b != r->bases_end(); ++b) {
+ if (b->getType()->isTemplateTypeParmType()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+//TODO: current implementation is not at all general, just tests what we
+// encounter in practice:
+bool hasBoolOverload(FunctionDecl const * decl) {
+ if (isa<CXXMethodDecl>(decl)) {
+ unsigned n = decl->getNumParams();
+ CXXRecordDecl const * r = dyn_cast<CXXRecordDecl>(
+ decl->getDeclContext());
+ assert(r != nullptr);
+ for (auto m = r->method_begin(); m != r->method_end(); ++m) {
+ if (m->getDeclName() == decl->getDeclName()
+ && m->getNumParams() == n)
+ {
+ bool hasSB = false;
+ for (unsigned i = 0; i != n; ++i) {
+ QualType t1 { decl->getParamDecl(i)->getType() };
+ bool isSB = isSalBool(t1);
+ bool isSBRef = !isSB && t1->isReferenceType()
+ && isSalBool(t1.getNonReferenceType());
+ QualType t2 { m->getParamDecl(i)->getType() };
+ if (!(isSB
+ ? t2->isBooleanType()
+ : isSBRef
+ ? (t2->isReferenceType()
+ && t2.getNonReferenceType()->isBooleanType())
+ : t2 == t1))
+ {
+ goto next;
+ }
+ hasSB |= isSB || isSBRef;
+ }
+ return hasSB;
+ // cheaply protect against the case where decl would have no
+ // sal_Bool parameters at all and would match itself
+ next:;
+ }
+ }
+ }
+ return false;
+}
+
+class SalBool:
+ public RecursiveASTVisitor<SalBool>, public loplugin::RewritePlugin
+{
+public:
+ explicit SalBool(InstantiationData const & data): RewritePlugin(data) {}
+
+ virtual void run() override;
+
+ bool VisitUnaryAddrOf(UnaryOperator const * op);
+
+ bool VisitCStyleCastExpr(CStyleCastExpr * expr);
+
+ bool VisitCXXStaticCastExpr(CXXStaticCastExpr * expr);
+
+ bool VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr * expr);
+
+ bool WalkUpFromParmVarDecl(ParmVarDecl const * decl);
+ bool VisitParmVarDecl(ParmVarDecl const * decl);
+
+ bool WalkUpFromVarDecl(VarDecl const * decl);
+ bool VisitVarDecl(VarDecl const * decl);
+
+ bool WalkUpFromFieldDecl(FieldDecl const * decl);
+ bool VisitFieldDecl(FieldDecl const * decl);
+
+ bool WalkUpFromFunctionDecl(FunctionDecl const * decl);
+ bool VisitFunctionDecl(FunctionDecl const * decl);
+
+ bool VisitValueDecl(ValueDecl const * decl);
+
+private:
+ bool isInUnoIncludeFile(SourceLocation spellingLocation) const;
+
+ bool isInSpecialMainFile(SourceLocation spellingLocation) const;
+
+ bool isInMainFile(SourceLocation spellingLocation) const;
+
+ bool isMacroBodyExpansion(SourceLocation location) const;
+
+ bool rewrite(SourceLocation location);
+
+ std::set<VarDecl const *> varDecls_;
+};
+
+void SalBool::run() {
+ if (compiler.getLangOpts().CPlusPlus) {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ for (auto decl: varDecls_) {
+ SourceLocation loc { decl->getLocStart() };
+ TypeSourceInfo * tsi = decl->getTypeSourceInfo();
+ if (tsi != nullptr) {
+ SourceLocation l {
+ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getBeginLoc()) };
+ SourceLocation end {
+ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getEndLoc()) };
+ assert(l.isFileID() && end.isFileID());
+ if (l == end
+ || compiler.getSourceManager().isBeforeInTranslationUnit(
+ l, end))
+ {
+ for (;;) {
+ unsigned n = Lexer::MeasureTokenLength(
+ l, compiler.getSourceManager(),
+ compiler.getLangOpts());
+ std::string s {
+ compiler.getSourceManager().getCharacterData(l),
+ n };
+ if (s == "sal_Bool") {
+ loc = l;
+ break;
+ }
+ if (l == end) {
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ }
+ }
+ if (!rewrite(loc)) {
+ report(
+ DiagnosticsEngine::Warning,
+ "VarDecl, use \"bool\" instead of \"sal_Bool\"", loc)
+ << decl->getSourceRange();
+ }
+ }
+ }
+}
+
+bool SalBool::VisitUnaryAddrOf(UnaryOperator const * op) {
+ Expr const * e1 = op->getSubExpr()->IgnoreParenCasts();
+ if (isSalBool(e1->getType())) {
+ DeclRefExpr const * e2 = dyn_cast<DeclRefExpr>(e1);
+ if (e2 != nullptr) {
+ VarDecl const * d = dyn_cast<VarDecl>(e2->getDecl());
+ if (d != nullptr) {
+ varDecls_.erase(d);
+ }
+ }
+ }
+ return true;
+}
+
+bool SalBool::VisitCStyleCastExpr(CStyleCastExpr * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (isSalBool(expr->getType())) {
+ SourceLocation loc { expr->getLocStart() };
+ while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
+ loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
+ }
+ if (isMacroBodyExpansion(loc)) {
+ StringRef name { Lexer::getImmediateMacroName(
+ loc, compiler.getSourceManager(), compiler.getLangOpts()) };
+ if (name == "sal_False" || name == "sal_True") {
+ return true;
+ }
+ }
+ report(
+ DiagnosticsEngine::Warning,
+ "CStyleCastExpr, suspicious cast from %0 to %1",
+ expr->getLocStart())
+ << expr->getSubExpr()->IgnoreParenImpCasts()->getType()
+ << expr->getType() << expr->getSourceRange();
+ }
+ return true;
+}
+
+bool SalBool::VisitCXXStaticCastExpr(CXXStaticCastExpr * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (isSalBool(expr->getType())) {
+ report(
+ DiagnosticsEngine::Warning,
+ "CStyleCastExpr, suspicious cast from %0 to %1",
+ expr->getLocStart())
+ << expr->getSubExpr()->IgnoreParenImpCasts()->getType()
+ << expr->getType() << expr->getSourceRange();
+ }
+ return true;
+}
+
+bool SalBool::VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (isSalBool(expr->getType())) {
+ report(
+ DiagnosticsEngine::Warning,
+ "CStyleCastExpr, suspicious cast from %0 to %1",
+ expr->getLocStart())
+ << expr->getSubExpr()->IgnoreParenImpCasts()->getType()
+ << expr->getType() << expr->getSourceRange();
+ }
+ return true;
+}
+
+bool SalBool::WalkUpFromParmVarDecl(ParmVarDecl const * decl) {
+ return VisitParmVarDecl(decl);
+}
+
+bool SalBool::VisitParmVarDecl(ParmVarDecl const * decl) {
+ if (ignoreLocation(decl)) {
+ return true;
+ }
+ if (isSalBool(decl->getType().getNonReferenceType())) {
+ FunctionDecl const * f = dyn_cast<FunctionDecl>(decl->getDeclContext());
+ if (f != nullptr) { // e.g.: typedef sal_Bool (* FuncPtr )( sal_Bool );
+ f = f->getCanonicalDecl();
+ if (!(hasCLanguageLinkageType(f)
+ || (isInUnoIncludeFile(
+ compiler.getSourceManager().getSpellingLoc(
+ f->getNameInfo().getLoc()))
+ && (!f->isInlined() || f->hasAttr<DeprecatedAttr>()
+ || decl->getType()->isReferenceType()
+ || hasBoolOverload(f)))
+ || f->isDeleted()))
+ {
+ CXXMethodDecl const * m = dyn_cast<CXXMethodDecl>(f);
+ //XXX: canOverrideTemplateArgumentMember(m) does not require
+ // !m->isVirtual()
+ if ((m == nullptr || !m->isVirtual()))
+ {
+ SourceLocation loc { decl->getLocStart() };
+ TypeSourceInfo * tsi = decl->getTypeSourceInfo();
+ if (tsi != nullptr) {
+ SourceLocation l {
+ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getBeginLoc()) };
+ SourceLocation end {
+ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getEndLoc()) };
+ assert(l.isFileID() && end.isFileID());
+ if (l == end
+ || (compiler.getSourceManager()
+ .isBeforeInTranslationUnit(l, end)))
+ {
+ for (;;) {
+ unsigned n = Lexer::MeasureTokenLength(
+ l, compiler.getSourceManager(),
+ compiler.getLangOpts());
+ std::string s {
+ compiler.getSourceManager().getCharacterData(l),
+ n };
+ if (s == "sal_Bool") {
+ loc = l;
+ break;
+ }
+ if (l == end) {
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ }
+ }
+ // Only rewrite declarations in include files if a
+ // definition is also seen, to avoid compilation of a
+ // definition (in a main file only processed later) to fail
+ // with a "mismatch" error before the rewriter had a chance
+ // to act upon the definition (but use the heuristic of
+ // assuming pure virtual functions do not have definitions):
+ if (!((isInMainFile(
+ compiler.getSourceManager().getSpellingLoc(
+ dyn_cast<FunctionDecl>(
+ decl->getDeclContext())
+ ->getNameInfo().getLoc()))
+ || f->isDefined() || f->isPure())
+ && rewrite(loc)))
+ {
+ report(
+ DiagnosticsEngine::Warning,
+ "ParmVarDecl, use \"bool\" instead of \"sal_Bool\"",
+ loc)
+ << decl->getSourceRange();
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool SalBool::WalkUpFromVarDecl(VarDecl const * decl) {
+ return VisitVarDecl(decl);
+}
+
+bool SalBool::VisitVarDecl(VarDecl const * decl) {
+ if (ignoreLocation(decl)) {
+ return true;
+ }
+ if (!decl->isExternC() && isSalBool(decl->getType())
+ && !isInSpecialMainFile(
+ compiler.getSourceManager().getSpellingLoc(decl->getLocStart())))
+ {
+ varDecls_.insert(decl);
+ }
+ return true;
+}
+
+bool SalBool::WalkUpFromFieldDecl(FieldDecl const * decl) {
+ return VisitFieldDecl(decl);
+}
+
+bool SalBool::VisitFieldDecl(FieldDecl const * decl) {
+ if (ignoreLocation(decl)) {
+ return true;
+ }
+ if (isSalBool(decl->getType())) {
+ TagDecl const * td = dyn_cast<TagDecl>(decl->getDeclContext());
+ assert(td != nullptr);
+ if (!(((td->isStruct() || td->isUnion())
+ && compat::isExternCContext(*td))
+ || isInUnoIncludeFile(
+ compiler.getSourceManager().getSpellingLoc(
+ decl->getLocation()))))
+ {
+ SourceLocation loc { decl->getLocStart() };
+ TypeSourceInfo * tsi = decl->getTypeSourceInfo();
+ if (tsi != nullptr) {
+ SourceLocation l {
+ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getBeginLoc()) };
+ SourceLocation end {
+ compiler.getSourceManager().getExpansionLoc(
+ tsi->getTypeLoc().getEndLoc()) };
+ assert(l.isFileID() && end.isFileID());
+ if (l == end
+ || compiler.getSourceManager().isBeforeInTranslationUnit(
+ l, end))
+ {
+ for (;;) {
+ unsigned n = Lexer::MeasureTokenLength(
+ l, compiler.getSourceManager(),
+ compiler.getLangOpts());
+ std::string s {
+ compiler.getSourceManager().getCharacterData(l),
+ n };
+ if (s == "sal_Bool") {
+ loc = l;
+ break;
+ }
+ if (l == end) {
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ }
+ }
+ if (!rewrite(loc)) {
+ report(
+ DiagnosticsEngine::Warning,
+ "FieldDecl, use \"bool\" instead of \"sal_Bool\"", loc)
+ << decl->getSourceRange();
+ }
+ }
+ }
+ return true;
+}
+
+bool SalBool::WalkUpFromFunctionDecl(FunctionDecl const * decl) {
+ return VisitFunctionDecl(decl);
+}
+
+bool SalBool::VisitFunctionDecl(FunctionDecl const * decl) {
+ if (ignoreLocation(decl)) {
+ return true;
+ }
+ if (isSalBool(compat::getReturnType(*decl).getNonReferenceType())) {
+ FunctionDecl const * f = decl->getCanonicalDecl();
+ CXXMethodDecl const * m = dyn_cast<CXXMethodDecl>(f);
+ //XXX: canOverrideTemplateArgumentMember(m) does not require
+ // !m->isVirtual()
+ if ((m == nullptr || !m->isVirtual()
+ || (m->size_overridden_methods() == 0
+ && !canOverrideTemplateArgumentMember(m)))
+ && !(hasCLanguageLinkageType(f)
+ || (isInUnoIncludeFile(
+ compiler.getSourceManager().getSpellingLoc(
+ f->getNameInfo().getLoc()))
+ && (!f->isInlined() || f->hasAttr<DeprecatedAttr>()))))
+ {
+ SourceLocation loc { decl->getLocStart() };
+ SourceLocation l { compiler.getSourceManager().getExpansionLoc(
+ loc) };
+ SourceLocation end { compiler.getSourceManager().getExpansionLoc(
+ decl->getNameInfo().getLoc()) };
+ assert(l.isFileID() && end.isFileID());
+ if (compiler.getSourceManager().isBeforeInTranslationUnit(l, end)) {
+ while (l != end) {
+ unsigned n = Lexer::MeasureTokenLength(
+ l, compiler.getSourceManager(), compiler.getLangOpts());
+ std::string s {
+ compiler.getSourceManager().getCharacterData(l), n };
+ if (s == "sal_Bool") {
+ loc = l;
+ break;
+ }
+ l = l.getLocWithOffset(std::max<unsigned>(n, 1));
+ }
+ }
+ // Only rewrite declarations in include files if a definition is
+ // also seen, to avoid compilation of a definition (in a main file
+ // only processed later) to fail with a "mismatch" error before the
+ // rewriter had a chance to act upon the definition (but use the
+ // heuristic of assuming pure virtual functions do not have
+ // definitions):
+ if (!((isInMainFile(
+ compiler.getSourceManager().getSpellingLoc(
+ decl->getNameInfo().getLoc()))
+ || f->isDefined() || f->isPure())
+ && rewrite(loc)))
+ {
+ report(
+ DiagnosticsEngine::Warning,
+ "use \"bool\" instead of \"sal_Bool\" as return type", loc)
+ << decl->getSourceRange();
+ }
+ }
+ }
+ return true;
+}
+
+bool SalBool::VisitValueDecl(ValueDecl const * decl) {
+ if (ignoreLocation(decl)) {
+ return true;
+ }
+ if (isSalBool(decl->getType()) && !rewrite(decl->getLocStart())) {
+ report(
+ DiagnosticsEngine::Warning,
+ "ValueDecl, use \"bool\" instead of \"sal_Bool\"",
+ decl->getLocStart())
+ << decl->getSourceRange();
+ }
+ return true;
+}
+
+bool SalBool::isInUnoIncludeFile(SourceLocation spellingLocation) const {
+ StringRef name {
+ compiler.getSourceManager().getFilename(spellingLocation) };
+ return isInMainFile(spellingLocation)
+ ? (name == SRCDIR "/cppu/source/cppu/compat.cxx"
+ || name == SRCDIR "/cppuhelper/source/compat.cxx"
+ || name == SRCDIR "/sal/osl/all/compat.cxx")
+ : (name.startswith(SRCDIR "/include/com/")
+ || name.startswith(SRCDIR "/include/cppu/")
+ || name.startswith(SRCDIR "/include/cppuhelper/")
+ || name.startswith(SRCDIR "/include/osl/")
+ || name.startswith(SRCDIR "/include/rtl/")
+ || name.startswith(SRCDIR "/include/sal/")
+ || name.startswith(SRCDIR "/include/salhelper/")
+ || name.startswith(SRCDIR "/include/systools/")
+ || name.startswith(SRCDIR "/include/typelib/")
+ || name.startswith(SRCDIR "/include/uno/"));
+}
+
+bool SalBool::isInSpecialMainFile(SourceLocation spellingLocation) const {
+ return isInMainFile(spellingLocation)
+ && (compiler.getSourceManager().getFilename(spellingLocation)
+ == SRCDIR "/cppu/qa/test_any.cxx");
+}
+
+bool SalBool::isInMainFile(SourceLocation spellingLocation) const {
+#if (__clang_major__ == 3 && __clang_minor__ >= 4) || __clang_major__ > 3
+ return compiler.getSourceManager().isInMainFile(spellingLocation);
+#else
+ return compiler.getSourceManager().isFromMainFile(spellingLocation);
+#endif
+}
+
+bool SalBool::isMacroBodyExpansion(SourceLocation location) const {
+#if (__clang_major__ == 3 && __clang_minor__ >= 3) || __clang_major__ > 3
+ return compiler.getSourceManager().isMacroBodyExpansion(location);
+#else
+ return location.isMacroID()
+ && !compiler.getSourceManager().isMacroArgExpansion(location);
+#endif
+}
+
+bool SalBool::rewrite(SourceLocation location) {
+ if (rewriter != nullptr) {
+ //TODO: "::sal_Bool" -> "bool", not "::bool"
+ SourceLocation loc { compiler.getSourceManager().getExpansionLoc(
+ location) };
+ unsigned n = Lexer::MeasureTokenLength(
+ loc, compiler.getSourceManager(), compiler.getLangOpts());
+ if (std::string(compiler.getSourceManager().getCharacterData(loc), n)
+ == "sal_Bool")
+ {
+ return replaceText(loc, n, "bool");
+ }
+ }
+ return false;
+}
+
+loplugin::Plugin::Registration<SalBool> X("salbool", true);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */