summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/store/sfxitemsetrewrite.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'compilerplugins/clang/store/sfxitemsetrewrite.cxx')
-rw-r--r--compilerplugins/clang/store/sfxitemsetrewrite.cxx419
1 files changed, 419 insertions, 0 deletions
diff --git a/compilerplugins/clang/store/sfxitemsetrewrite.cxx b/compilerplugins/clang/store/sfxitemsetrewrite.cxx
new file mode 100644
index 000000000..e1fa52209
--- /dev/null
+++ b/compilerplugins/clang/store/sfxitemsetrewrite.cxx
@@ -0,0 +1,419 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <sstream>
+#include <tuple>
+#include <vector>
+
+#include "check.hxx"
+#include "plugin.hxx"
+
+namespace {
+
+bool gap(APSInt n1, APSInt n2) { return n1 < n2 && n2 - n1 > 1; }
+
+class Visitor final:
+ public loplugin::FilteringRewritePlugin<Visitor>
+{
+public:
+ explicit Visitor(InstantiationData const & data): FilteringRewritePlugin(data) {}
+
+ bool VisitCXXConstructExpr(CXXConstructExpr const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ auto const ctor = expr->getConstructor();
+ if (!loplugin::DeclCheck(ctor->getParent()).Class("SfxItemSet")
+ .GlobalNamespace())
+ {
+ return true;
+ }
+ auto const numParams = ctor->getNumParams();
+ auto const variadic = ctor->isVariadic();
+ if (!(((numParams == 3 && !variadic) || (numParams == 4 && variadic))
+ && (loplugin::TypeCheck(ctor->getParamDecl(0)->getType())
+ .LvalueReference().Class("SfxItemPool").GlobalNamespace())))
+ {
+ return true;
+ }
+ auto const numArgs = expr->getNumArgs();
+ if (numArgs < 3) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected SfxItemPool constructor call with less than three"
+ " arguments"),
+ expr->getExprLoc())
+ << expr->getSourceRange();
+ return true;
+ }
+ rewrite(expr, variadic, false, numArgs, expr->getArgs());
+ return true;
+ }
+
+ bool VisitCallExpr(CallExpr const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ auto const dre = dyn_cast<DeclRefExpr>(
+ expr->getCallee()->IgnoreParenImpCasts());
+ if (dre == nullptr
+ || !(loplugin::DeclCheck(dre->getDecl()).Function("make_unique")
+ .Namespace("o3tl").GlobalNamespace()))
+ {
+ return true;
+ }
+ auto const numTArgs = dre->getNumTemplateArgs();
+ if (numTArgs == 0) {
+ report(
+ DiagnosticsEngine::Warning,
+ "unexpected o3tl::make_unique call without template arguments",
+ expr->getExprLoc())
+ << expr->getSourceRange();
+ return true;
+ }
+ auto const tArg0 = dre->getTemplateArgs()[0].getArgument();
+ if (tArg0.getKind() != TemplateArgument::Type) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected o3tl::make_unique call with non-type first"
+ " template argument"),
+ expr->getExprLoc())
+ << expr->getSourceRange();
+ return true;
+ }
+ if (!loplugin::TypeCheck(tArg0.getAsType()).Class("SfxItemSet")
+ .GlobalNamespace())
+ {
+ return true;
+ }
+ auto const numArgs = expr->getNumArgs();
+ if (numArgs < 3) {
+ return true;
+ }
+ rewrite(expr, numArgs > 3, true, numArgs, expr->getArgs());
+ return true;
+ }
+
+ bool VisitTemplateSpecializationTypeLoc(
+ TemplateSpecializationTypeLoc typeLoc)
+ {
+ auto const loc = typeLoc.getBeginLoc();
+ if (loc.isInvalid() || ignoreLocation(loc)) {
+ return true;
+ }
+ if (!loplugin::TypeCheck(typeLoc.getType()).Struct("Items")
+ .Namespace("svl").GlobalNamespace())
+ {
+ return true;
+ }
+ unsigned const numArgs = typeLoc.getNumArgs();
+ if (numArgs == 0) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected svl::Items specialization with zero template"
+ " arguments"),
+ loc)
+ << typeLoc.getSourceRange();
+ return true;
+ }
+ if (numArgs % 2 == 1) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected svl::Items specialization with odd number of"
+ " template arguments"),
+ loc)
+ << typeLoc.getSourceRange();
+ return true;
+ }
+ std::vector<Range> ranges;
+ auto good = true;
+ APSInt prev;
+ for (unsigned i = 0; i != numArgs; ++i) {
+ auto const argLoc = typeLoc.getArgLoc(i);
+ auto const & arg = argLoc.getArgument();
+ APSInt v;
+ switch (arg.getKind()) {
+ case TemplateArgument::Integral:
+ v = arg.getAsIntegral();
+ break;
+ case TemplateArgument::Expression:
+ if (arg.getAsExpr()->EvaluateAsInt(v, compiler.getASTContext()))
+ {
+ break;
+ }
+ // [[fallthrough]];
+ default:
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected svl::Items specialization with non-integral"
+ " template argument %0"),
+ argLoc.getLocation())
+ << (i + 1)
+ << typeLoc.getSourceRange();
+ return true;
+ }
+ if (i % 2 == 0) {
+ good = good && (i == 0 || gap(prev, v));
+ } else {
+ if (v < prev) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected svl::Items specialization with template"
+ " argument %0 smaller than previous one, %1 < %2"),
+ argLoc.getLocation())
+ << (i + 1) << v.toString(10) << prev.toString(10)
+ << typeLoc.getSourceRange();
+ return true;
+ }
+ ranges.emplace_back(prev, v, (i / 2) + 1);
+ }
+ prev = v;
+ }
+ if (good) {
+ return true;
+ }
+ std::ostringstream buf1;
+ for (auto const i: ranges) {
+ buf1 << "\n ";
+ printBegin(buf1, typeLoc, i);
+ buf1 << " ... ";
+ printEnd(buf1, typeLoc, i);
+ }
+ std::sort(ranges.begin(), ranges.end());
+ std::ostringstream buf2;
+ for (auto i = ranges.begin(); i != ranges.end();) {
+ buf2 << "\n ";
+ printBegin(buf2, typeLoc, *i);
+ buf2 << " ... ";
+ auto end = *i;
+ for (;;) {
+ auto j = i + 1;
+ if (j == ranges.end() || gap(get<1>(end), get<0>(*j))) {
+ printEnd(buf2, typeLoc, end);
+ i = j;
+ break;
+ }
+ if (get<1>(*j) >= get<1>(end)) {
+ end = *j;
+ }
+ i = j;
+ }
+ }
+ report(
+ DiagnosticsEngine::Warning,
+ ("reorder svl::Items specialization template arguments from:%0\nto:"
+ "%1"),
+ loc)
+ << buf1.str() << buf2.str() << typeLoc.getSourceRange();
+ return true;
+ }
+
+private:
+ void run() override {
+ if (compiler.getLangOpts().CPlusPlus) {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+ }
+
+ SourceLocation nextToken(SourceLocation loc) {
+ return loc.getLocWithOffset(
+ Lexer::MeasureTokenLength(
+ loc, compiler.getSourceManager(), compiler.getLangOpts()));
+ }
+
+ SourceLocation atMacroExpansionStart(SourceLocation loc) {
+ while (loc.isMacroID()
+ && (compiler.getSourceManager()
+ .isAtStartOfImmediateMacroExpansion(loc, &loc)))
+ {}
+ return loc;
+ }
+
+ SourceLocation atMacroExpansionEnd(SourceLocation loc) {
+ while (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
+ loc = compiler.getSourceManager().getImmediateExpansionRange(loc)
+ .second;
+ }
+ return loc;
+ }
+
+ using Range = std::tuple<APSInt, APSInt, unsigned>;
+
+ void printSource(
+ std::ostringstream & s, TemplateSpecializationTypeLoc typeLoc, Range r,
+ bool end)
+ {
+ auto const argLoc = typeLoc.getArgLoc(
+ 2 * (get<2>(r) - 1) + (end ? 1 : 0));
+ auto const src1 = argLoc.getSourceRange();
+ auto const src2 = SourceRange(
+ atMacroExpansionStart(src1.getBegin()),
+ Lexer::getLocForEndOfToken(
+ compiler.getSourceManager().getExpansionLoc(
+ atMacroExpansionEnd(src1.getEnd())),
+ 0, compiler.getSourceManager(), compiler.getLangOpts()));
+ s << " '" << Lexer::getSourceText(
+ Lexer::getAsCharRange(
+ src2, compiler.getSourceManager(), compiler.getLangOpts()),
+ compiler.getSourceManager(), compiler.getLangOpts()).str()
+ << "'";
+ }
+
+ void printBegin(
+ std::ostringstream & s, TemplateSpecializationTypeLoc typeLoc, Range r)
+ {
+ s << get<2>(r) << "B: " << get<0>(r).toString(10);
+ printSource(s, typeLoc, r, false);
+ }
+
+ void printEnd(
+ std::ostringstream & s, TemplateSpecializationTypeLoc typeLoc, Range r)
+ {
+ s << get<2>(r) << "E: " << get<1>(r).toString(10);
+ printSource(s, typeLoc, r, true);
+ }
+
+ void rewrite(
+ Expr const * expr, bool variadic, bool forward, unsigned numArgs,
+ Expr const * const * args)
+ {
+ bool constant = true;
+ unsigned firstZero = 0;
+ for (unsigned i = 1; i != numArgs; ++i) {
+ auto const arg = args[i];
+ constant = constant
+ && arg->isCXX11ConstantExpr(compiler.getASTContext());
+ APSInt v;
+ auto const zero
+ = ((arg->EvaluateAsInt(v, compiler.getASTContext())
+ && v == 0)
+ || (variadic && i > 4
+ && arg->isNullPointerConstant(
+ compiler.getASTContext(),
+ Expr::NPC_ValueDependentIsNotNull)));
+ if (variadic) {
+ if (zero) {
+ if (firstZero == 0) {
+ if (i == 1) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected missing non-zero arguments before"
+ " first zero argument in SfxItemPool"
+ " constructor call"),
+ arg->getExprLoc())
+ << expr->getSourceRange();
+ return;
+ }
+ if (i % 2 == 0) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected odd number of potentially non-zero"
+ " arguments before first definitely zero"
+ " argument in SfxItemPool constructor call"),
+ arg->getExprLoc())
+ << expr->getSourceRange();
+ return;
+ }
+ firstZero = i;
+ }
+ } else if (firstZero != 0) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected potentially non-zero argument in"
+ " SfxItemPool constructor call, following zero"
+ " argument"),
+ arg->getExprLoc())
+ << expr->getSourceRange();
+ return;
+ }
+ } else if (zero) {
+ report(
+ DiagnosticsEngine::Warning,
+ "unexpected zero argument in SfxItemPool constructor call",
+ arg->getExprLoc())
+ << expr->getSourceRange();
+ return;
+ }
+ }
+ if (variadic && firstZero == 0) {
+ report(
+ DiagnosticsEngine::Warning,
+ ("unexpected SfxItemPool constructor call with no detectable"
+ " zero arguments"),
+ expr->getExprLoc())
+ << expr->getSourceRange();
+ return;
+ }
+ if (rewriter != nullptr) {
+ if (!insertTextBefore(
+ atMacroExpansionStart(args[1]->getLocStart()),
+ (constant
+ ? StringRef("svl::Items<")
+ : (forward
+ ? StringRef("std::initializer_list<SfxItemSet::Pair>{{")
+ : StringRef("{{")))))
+ {
+ goto failed;
+ }
+ auto const postLoc = atMacroExpansionEnd(
+ args[numArgs - 1]->getLocEnd());
+ auto const postStr = constant ? StringRef(">{}") : StringRef("}}");
+ if (variadic) {
+ //TODO: the replaced range can contain relevant comments:
+ if (!replaceText(
+ SourceRange(
+ nextToken(
+ atMacroExpansionEnd(
+ args[firstZero - 1]->getLocEnd())),
+ postLoc),
+ postStr))
+ {
+ goto failed;
+ }
+ } else {
+ if (!insertTextAfterToken(postLoc, postStr)) {
+ goto failed;
+ }
+ }
+ if (!constant && variadic) {
+ for (unsigned i = 2; i != firstZero - 1; ++i) {
+ auto const arg = args[i];
+ if (!(i % 2 == 0
+ ? insertTextAfterToken(
+ atMacroExpansionEnd(arg->getLocEnd()), "}")
+ : insertTextBefore(
+ atMacroExpansionStart(arg->getLocStart()), "{")))
+ {
+ goto failed;
+ }
+ }
+ }
+ return;
+ }
+ failed: //TODO: undo partial changes
+ report(
+ DiagnosticsEngine::Warning,
+ ("rewrite SfxItemPool constructor call with"
+ " %select{%select{|std::initializer_list<SfxItemSet::Pair>}1"
+ "%{%{..., ...%}, ..., %{..., ...%}%}|svl::Items<...>%{%}}0 around"
+ " the %select{|leading }2%3 WID arguments%select{| and remove the"
+ " remaining %4 zero %plural{1:argument|:arguments}4}2"),
+ expr->getExprLoc())
+ << constant << forward << variadic
+ << (variadic ? firstZero - 1 : numArgs - 1) << (numArgs - firstZero)
+ << expr->getSourceRange();
+ }
+};
+
+static loplugin::Plugin::Registration<Visitor> reg("sfxitemsetrewrite",true);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */