Extended loplugin:ostr: Automatic rewrite some O[U]StringLiteral -> O[U]String

Change-Id: I8c08bf41b96d4a6085e7d72cb39e629efa556d09
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158300
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
diff --git a/compilerplugins/clang/ostr.cxx b/compilerplugins/clang/ostr.cxx
index 4629166..9675913 100644
--- a/compilerplugins/clang/ostr.cxx
+++ b/compilerplugins/clang/ostr.cxx
@@ -37,9 +37,125 @@ public:

    void run() override
    {
        if (compiler.getLangOpts().CPlusPlus)
        if (compiler.getLangOpts().CPlusPlus
            && TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
        {
            TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
            for (auto const& i : vars_)
            {
                auto const utf16
                    = bool(loplugin::TypeCheck(i.first->getType()).Class("OUStringLiteral"));
                if (i.second.singleUse == nullptr)
                {
                    if (!(i.first->getDeclContext()->isFunctionOrMethod()
                          || compiler.getSourceManager().isInMainFile(i.first->getLocation())
                          || compiler.getDiagnosticOpts().VerifyDiagnostics))
                    {
                        //TODO, rewriting these in include files could trigger
                        // loplugin:redundantfcast in other translation units:
                        continue;
                    }
                    if (rewriter != nullptr)
                    {
                        auto e = i.first->getInit()->IgnoreParenImpCasts();
                        if (auto const e2 = dyn_cast<ConstantExpr>(e))
                        {
                            e = e2->getSubExpr()->IgnoreParenImpCasts();
                        }
                        if (auto const e2 = dyn_cast<CXXConstructExpr>(e))
                        {
                            assert(e2->getNumArgs() == 1);
                            e = e2->getArg(0)->IgnoreParenImpCasts();
                        }
                        e = dyn_cast<clang::StringLiteral>(e);
                        // e is null when this OUStringLiteral is initialized with another
                        // OUStringLiteral:
                        if (e == nullptr
                            || insertTextAfterToken(e->getEndLoc(), utf16 ? "_ustr" : "_ostr"))
                        {
                            auto ok = true;
                            for (auto d = i.first->getMostRecentDecl(); d != nullptr;
                                 d = d->getPreviousDecl())
                            {
                                auto const l1 = d->getTypeSpecStartLoc();
                                auto l2 = d->getTypeSpecEndLoc();
                                l2 = l2.getLocWithOffset(Lexer::MeasureTokenLength(
                                    l2, compiler.getSourceManager(), compiler.getLangOpts()));
                                if (!replaceText(l1, delta(l1, l2), utf16 ? "OUString" : "OString"))
                                {
                                    ok = false;
                                }
                            }
                            for (auto const i : i.second.explicitConversions)
                            {
                                auto const e2 = i->getArg(0);
                                auto l1 = i->getBeginLoc();
                                auto l2 = e2->getBeginLoc();
                                auto l3 = e2->getEndLoc();
                                auto l4 = i->getEndLoc();
                                while (compiler.getSourceManager().isMacroArgExpansion(l1)
                                       && compiler.getSourceManager().isMacroArgExpansion(l2)
                                       && compiler.getSourceManager().isMacroArgExpansion(l3)
                                       && compiler.getSourceManager().isMacroArgExpansion(l4))
                                //TODO: check all four locations are part of the same macro argument
                                // expansion
                                {
                                    l1 = compiler.getSourceManager().getImmediateMacroCallerLoc(l1);
                                    l2 = compiler.getSourceManager().getImmediateMacroCallerLoc(l2);
                                    l3 = compiler.getSourceManager().getImmediateMacroCallerLoc(l3);
                                    l4 = compiler.getSourceManager().getImmediateMacroCallerLoc(l4);
                                }
                                l3 = l3.getLocWithOffset(Lexer::MeasureTokenLength(
                                    l3, compiler.getSourceManager(), compiler.getLangOpts()));
                                l4 = l4.getLocWithOffset(Lexer::MeasureTokenLength(
                                    l4, compiler.getSourceManager(), compiler.getLangOpts()));
                                removeText(l1, delta(l1, l2));
                                removeText(l3, delta(l3, l4));
                            }
                            if (ok)
                            {
                                continue;
                            }
                        }
                    }
                    report(DiagnosticsEngine::Warning,
                           "use '%select{OString|OUString}0', created from a %select{_ostr|_ustr}0 "
                           "user-defined string literal, instead of "
                           "'%select{OStringLiteral|OUStringLiteral}0' for the variable %1",
                           i.first->getLocation())
                        << utf16 << i.first->getName() << i.first->getSourceRange();
                    for (auto d = i.first->getMostRecentDecl(); d != nullptr;
                         d = d->getPreviousDecl())
                    {
                        if (d != i.first)
                        {
                            report(DiagnosticsEngine::Note, "variable %0 declared here",
                                   d->getLocation())
                                << d->getName() << d->getSourceRange();
                        }
                    }
                }
                else
                {
                    if (!compiler.getDiagnosticOpts().VerifyDiagnostics)
                    {
                        //TODO, left for later:
                        continue;
                    }
                    report(DiagnosticsEngine::Warning,
                           "directly use a %select{_ostr|_ustr}0 user-defined string literal "
                           "instead of introducing the intermediary "
                           "'%select{OStringLiteral|OUStringLiteral}0' variable %1",
                           i.second.singleUse->getExprLoc())
                        << utf16 << i.first->getName() << i.second.singleUse->getSourceRange();
                    for (auto d = i.first->getMostRecentDecl(); d != nullptr;
                         d = d->getPreviousDecl())
                    {
                        report(DiagnosticsEngine::Note, "intermediary variable %0 declared here",
                               d->getLocation())
                            << d->getName() << d->getSourceRange();
                    }
                }
            }
        }
    }

@@ -67,16 +183,117 @@ public:
        return ret;
    }

    bool VisitVarDecl(VarDecl const* decl)
    {
        if (ignoreLocation(decl))
        {
            return true;
        }
        if (!decl->isThisDeclarationADefinition())
        {
            return true;
        }
        loplugin::TypeCheck const tc(decl->getType());
        if (!(tc.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
              || tc.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()))
        {
            return true;
        }
        if (suppressWarningAt(decl->getLocation()))
        {
            return true;
        }
        vars_[decl].multipleUses
            = decl->getDeclContext()->isFileContext()
                  ? !compiler.getSourceManager().isInMainFile(decl->getLocation())
                  : decl->isExternallyVisible();
        return true;
    }

    bool VisitDeclRefExpr(DeclRefExpr const* expr)
    {
        if (ignoreLocation(expr))
        {
            return true;
        }
        auto const d1 = dyn_cast<VarDecl>(expr->getDecl());
        if (d1 == nullptr)
        {
            return true;
        }
        auto const d2 = d1->getDefinition();
        if (d2 == nullptr)
        {
            return true;
        }
        auto const i = vars_.find(d2);
        if (i == vars_.end())
        {
            return true;
        }
        if (!i->second.multipleUses)
        {
            if (i->second.singleUse == nullptr)
            {
                i->second.singleUse = expr;
            }
            else
            {
                i->second.multipleUses = true;
                i->second.singleUse = nullptr;
            }
        }
        return true;
    }

    bool VisitCXXConstructExpr(CXXConstructExpr const* expr)
    {
        if (ignoreLocation(expr))
        {
            return true;
        }
        if (!loplugin::DeclCheck(expr->getConstructor()->getParent())
                 .Class("OUString")
                 .Namespace("rtl")
                 .GlobalNamespace())
        auto const dc = expr->getConstructor()->getParent();
        auto const utf16
            = bool(loplugin::DeclCheck(dc).Class("OUString").Namespace("rtl").GlobalNamespace());
        if (!(utf16 || loplugin::DeclCheck(dc).Class("OString").Namespace("rtl").GlobalNamespace()))
        {
            return true;
        }
        if (expr->getNumArgs() == 1
            && loplugin::TypeCheck(expr->getArg(0)->getType())
                   .Class(utf16 ? "OUStringLiteral" : "OStringLiteral")
                   .Namespace("rtl")
                   .GlobalNamespace())
        {
            if (functionalCasts_.empty()
                || functionalCasts_.top()->getSubExpr()->IgnoreImplicit() != expr)
            {
                return true;
            }
            auto const e = dyn_cast<DeclRefExpr>(expr->getArg(0)->IgnoreParenImpCasts());
            if (e == nullptr)
            {
                return true;
            }
            auto const d1 = dyn_cast<VarDecl>(e->getDecl());
            if (d1 == nullptr)
            {
                return true;
            }
            auto const d2 = d1->getDefinition();
            if (d2 == nullptr)
            {
                return true;
            }
            auto const i = vars_.find(d2);
            if (i == vars_.end())
            {
                return true;
            }
            i->second.explicitConversions.insert(expr);
            return true;
        }
        if (!utf16)
        {
            return true;
        }
@@ -195,9 +412,17 @@ private:
               - compiler.getSourceManager().getDecomposedLoc(loc1).second;
    }

    struct Var
    {
        bool multipleUses = false;
        DeclRefExpr const* singleUse = nullptr;
        std::set<CXXConstructExpr const*> explicitConversions;
    };

    std::set<Expr const*> defaultArgs_;
    std::stack<CXXFunctionalCastExpr const*> functionalCasts_;
    std::set<SourceLocation> locs_;
    std::map<VarDecl const*, Var> vars_;
};

loplugin::Plugin::Registration<Ostr> X("ostr", true);
diff --git a/compilerplugins/clang/test/ostr.cxx b/compilerplugins/clang/test/ostr.cxx
index 6a09728..28e2d74 100644
--- a/compilerplugins/clang/test/ostr.cxx
+++ b/compilerplugins/clang/test/ostr.cxx
@@ -53,6 +53,23 @@ void f()
    // expansion:
    // expected-error-re@+1 {{use a _ustr user-defined string literal instead of constructing an instance of '{{(rtl::)?}}OUString' from an ordinary string literal [loplugin:ostr]}}
    M("foo");

    // expected-note@+1 {{intermediary variable l1 declared here [loplugin:ostr]}}
    constexpr OStringLiteral l1("foo");
    // expected-error@+1 {{directly use a _ostr user-defined string literal instead of introducing the intermediary 'OStringLiteral' variable l1 [loplugin:ostr]}}
    (void)l1;
    // expected-error@+1 {{use 'OString', created from a _ostr user-defined string literal, instead of 'OStringLiteral' for the variable l2 [loplugin:ostr]}}
    constexpr OStringLiteral l2("foo");
    (void)l2;
    (void)l2;
    // expected-note@+1 {{intermediary variable l3 declared here [loplugin:ostr]}}
    OUStringLiteral l3(u"foo");
    // expected-error@+1 {{directly use a _ustr user-defined string literal instead of introducing the intermediary 'OUStringLiteral' variable l3 [loplugin:ostr]}}
    (void)l3;
    // expected-error@+1 {{use 'OUString', created from a _ustr user-defined string literal, instead of 'OUStringLiteral' for the variable l4 [loplugin:ostr]}}
    OUStringLiteral l4(u"foo");
    (void)l4;
    (void)l4;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */