Files
Stephan Bergmann 060c1b69ef Adapt compilerplugins to recent LLVM 22 trunk change
...<https://github.com/llvm/llvm-project/commit/91cdd35008e9ab32dffb7e401cdd7313b3461892>
"[clang] Improve nested name specifier AST representation (#147835)".

For one, clang::TypeDecl::getTypeForDecl no longer works for TagDecls, so the
loplugin::TypeCheck ctor taking a TypeDecl started to fail at runtime.  So heed
the advice given in the comment to clang::TypeDecl::getTypeForDecl and instead
"ckeck out ASTContext::getTypeDeclType or one of...", which means that we now
need to transport a clang::ASTContext to all those places that want to create
such a loplugin::TypeCheck.

For another, clang::TagType::getDecl ("renamed to `getOriginalDecl`" for now) no
longer returns one dedicated Decl per type (so its results could be compared for
equality), so wrap that in compat::getDecl to do an additional getCanonicalDecl.
(But note how that requires an additional compat::getDefinitionOrSelf across the
various loplugins' isWarnUnusedType implementations, to check for any attributes
on the actual definitions rather than on whatever first declarations happen to
be considered the canonical decls.)

And the visitor's TraverseDecltypeTypeLoc takes an additional bool
TraverseQualifier parameter now, so adapt the sharedvisitor machinery
accordingly.

Plus some bespoke fixes across various loplugins.

loplugin:indentation is hit by
<https://github.com/llvm/llvm-project/issues/153540> "Clang getBeginLoc of some
CXXConstructExpr has moved past namespace prefix" (which would cause false

> core/dbaccess/source/core/recovery/subcomponentrecovery.cxx:546:18: error: statement mis-aligned compared to neighbours  [loplugin:indentation]
>   546 |             rtl::Reference( new SubComponentLoader( xController, xDocDefinition ) );
>       |                  ^
> core/dbaccess/source/core/recovery/subcomponentrecovery.cxx:545:13: note: measured against this one [loplugin:indentation]
>   545 |             Reference< XController > xController( m_xDocumentUI, UNO_QUERY_THROW );
>       |             ^

etc.), so is disabled for Clang 22 for now, until that bug is fixed.

(Various diagnostics are slightly different now, leading to some more
expected-error-re variation in the tests.  And loplugin::refcounting no longer
duplicates its warnings, which is probably a feature and not a bug.)

Change-Id: I9fe152e47ee571ad57c7c57f0eaa9c10e620f03e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189694
Reviewed-by: Stephan Bergmann <stephan.bergmann@collabora.com>
Tested-by: Jenkins
2025-08-17 19:42:56 +02:00

560 lines
20 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* Based on LLVM/Clang.
*
* This file is distributed under the University of Illinois Open Source
* License. See LICENSE.TXT for details.
*
*/
#ifndef LO_CLANG_SHARED_PLUGINS
#include "plugin.hxx"
#include "check.hxx"
#include "compat.hxx"
#include "config_clang.h"
#include <iostream>
/*
This is a compile-time checker.
Check for cases where we have
- two IDL interfaces A and B,
- B extends A
- we are converting a Reference<B> to a Reference<A> using UNO_QUERY
This makes the code simpler and cheaper, because UNO_QUERY can be surprisingly expensive if used a lot.
*/
namespace
{
class ReferenceCasting : public loplugin::FilteringPlugin<ReferenceCasting>
{
public:
explicit ReferenceCasting(loplugin::InstantiationData const& data)
: FilteringPlugin(data)
{
}
bool preRun() override
{
std::string fn(handler.getMainFileName());
loplugin::normalizeDotDotInFilePath(fn);
// macros
if (fn == SRCDIR "/dbaccess/source/ui/browser/formadapter.cxx")
return false;
// UNO aggregation
if (fn == SRCDIR "/toolkit/source/controls/stdtabcontroller.cxx")
return false;
return true;
}
void run() override
{
if (preRun())
{
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
}
}
bool VisitCXXConstructExpr(const CXXConstructExpr* cce);
bool VisitCXXMemberCallExpr(const CXXMemberCallExpr* mce);
bool VisitCallExpr(const CallExpr*);
bool VisitInitListExpr(const InitListExpr*);
private:
bool CheckForUnnecessaryGet(const Expr*, bool includeRtlReference);
};
static const RecordType* extractTemplateType(QualType);
static bool isDerivedFrom(const CXXRecordDecl* subtypeRecord, const CXXRecordDecl* baseRecord);
bool ReferenceCasting::VisitInitListExpr(const InitListExpr* ile)
{
if (ignoreLocation(ile))
return true;
for (const Expr* expr : ile->inits())
{
if (CheckForUnnecessaryGet(expr, /*includeRtlReference*/ true))
{
report(DiagnosticsEngine::Warning, "unnecessary get() call", expr->getBeginLoc())
<< expr->getSourceRange();
return true;
}
}
return true;
}
bool ReferenceCasting::VisitCXXConstructExpr(const CXXConstructExpr* cce)
{
if (ignoreLocation(cce))
return true;
// don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
StringRef aFileName
= getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(cce->getBeginLoc()));
if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.h"))
return true;
if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.hxx"))
return true;
if (cce->getNumArgs() == 0)
return true;
// look for calls to the Reference<T>(x, UNO_something) constructor
auto constructorClass = cce->getConstructor()->getParent();
auto dc = loplugin::DeclCheck(constructorClass);
bool isUnoReference(dc.Class("Reference").Namespace("uno"));
bool isRtlReference(dc.Class("Reference").Namespace("rtl").GlobalNamespace());
if (!isUnoReference && !isRtlReference)
return true;
if (isUnoReference)
if (CheckForUnnecessaryGet(cce->getArg(0), /*includeRtlReference*/ cce->getNumArgs() == 1))
{
report(DiagnosticsEngine::Warning, "unnecessary get() call",
cce->getArg(0)->getBeginLoc())
<< cce->getArg(0)->getSourceRange();
return true;
}
if (isRtlReference && cce->getNumArgs() == 1)
if (CheckForUnnecessaryGet(cce->getArg(0), /*includeRtlReference*/ true))
{
report(DiagnosticsEngine::Warning, "unnecessary get() call",
cce->getArg(0)->getBeginLoc())
<< cce->getArg(0)->getSourceRange();
return true;
}
if (isRtlReference)
return true;
if (isUnoReference && cce->getNumArgs() != 2)
return true;
// ignore the up-casting constructor, which has a std::enable_if second parameter
if (isUnoReference && cce->getNumArgs() == 2
&& !cce->getConstructor()->getParamDecl(1)->getType()->isEnumeralType())
return true;
// extract the type parameter passed to the template
const RecordType* templateParamType = extractTemplateType(cce->getType());
if (!templateParamType)
return true;
// extract the type of the first parameter passed to the constructor
const Expr* constructorArg0 = cce->getArg(0);
if (!constructorArg0)
return true;
// drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
QualType argType;
for (;;)
{
if (auto castExpr = dyn_cast<CastExpr>(constructorArg0))
{
constructorArg0 = castExpr->getSubExprAsWritten();
continue;
}
if (auto matTempExpr = dyn_cast<MaterializeTemporaryExpr>(constructorArg0))
{
constructorArg0 = matTempExpr->getSubExpr();
continue;
}
if (auto bindTempExpr = dyn_cast<CXXBindTemporaryExpr>(constructorArg0))
{
constructorArg0 = bindTempExpr->getSubExpr();
continue;
}
if (auto tempObjExpr = dyn_cast<CXXTemporaryObjectExpr>(constructorArg0))
{
constructorArg0 = tempObjExpr->getArg(0);
continue;
}
if (auto parenExpr = dyn_cast<ParenExpr>(constructorArg0))
{
constructorArg0 = parenExpr->getSubExpr();
continue;
}
// for the "uno::Reference<X>(*this, UNO_QUERY)" case
if (auto unaryOper = dyn_cast<UnaryOperator>(constructorArg0))
{
if (unaryOper->getOpcode() == UO_Deref)
{
constructorArg0 = unaryOper->getSubExpr();
continue;
}
}
argType = constructorArg0->getType();
break;
}
const RecordType* argTemplateType = extractTemplateType(argType);
if (!argTemplateType)
return true;
CXXRecordDecl const* templateParamRD
= dyn_cast<CXXRecordDecl>(compat::getDecl(templateParamType));
CXXRecordDecl const* constructorArgRD
= dyn_cast<CXXRecordDecl>(compat::getDecl(argTemplateType));
// querying for XInterface (instead of doing an upcast) has special semantics,
// to check for UNO object equivalence.
if (templateParamRD->getName() == "XInterface")
return true;
// XShape is used in UNO aggregates in very "entertaining" ways, which means an UNO_QUERY
// can return a completely different object, e.g. see SwXShape::queryInterface
if (templateParamRD->getName() == "XShape")
return true;
if (cce->getNumArgs() == 2)
if (auto declRefExpr = dyn_cast<DeclRefExpr>(cce->getArg(1)))
{
// no warning expected, used to reject null references
if (auto enumConstantDecl = dyn_cast<EnumConstantDecl>(declRefExpr->getDecl()))
{
if (enumConstantDecl->getName() == "UNO_SET_THROW")
return true;
if (enumConstantDecl->getName() == "UNO_QUERY_THROW")
return true;
if (enumConstantDecl->getName() == "SAL_NO_ACQUIRE")
return true;
}
}
if (constructorArgRD->Equals(templateParamRD)
|| isDerivedFrom(constructorArgRD, templateParamRD))
{
report(DiagnosticsEngine::Warning,
"the source reference is already a subtype of the destination reference, just use =",
cce->getBeginLoc())
<< cce->getSourceRange();
}
return true;
}
bool ReferenceCasting::VisitCXXMemberCallExpr(const CXXMemberCallExpr* mce)
{
if (ignoreLocation(mce))
return true;
// don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
StringRef aFileName
= getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(mce->getBeginLoc()));
if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.h"))
return true;
if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.hxx"))
return true;
if (mce->getNumArgs() == 0)
return true;
// look for calls to the Reference<T>.set(x, UNO_QUERY) constructor
auto method = mce->getMethodDecl();
if (!method || !method->getIdentifier() || method->getName() != "set")
return true;
auto methodRecordDecl = dyn_cast<ClassTemplateSpecializationDecl>(mce->getRecordDecl());
if (!methodRecordDecl || !methodRecordDecl->getIdentifier()
|| methodRecordDecl->getName() != "Reference")
return true;
if (CheckForUnnecessaryGet(mce->getArg(0), /*includeRtlReference*/ mce->getNumArgs() == 1))
{
report(DiagnosticsEngine::Warning, "unnecessary get() call", mce->getArg(0)->getBeginLoc())
<< mce->getArg(0)->getSourceRange();
return true;
}
if (mce->getNumArgs() != 2)
return true;
// extract the type parameter passed to the template
const RecordType* templateParamType
= dyn_cast<RecordType>(methodRecordDecl->getTemplateArgs()[0].getAsType());
if (!templateParamType)
return true;
// extract the type of the first parameter passed to the method
const Expr* arg0 = mce->getArg(0);
if (!arg0)
return true;
// drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
QualType argType;
for (;;)
{
if (auto castExpr = dyn_cast<CastExpr>(arg0))
{
arg0 = castExpr->getSubExpr();
continue;
}
if (auto matTempExpr = dyn_cast<MaterializeTemporaryExpr>(arg0))
{
arg0 = matTempExpr->getSubExpr();
continue;
}
if (auto bindTempExpr = dyn_cast<CXXBindTemporaryExpr>(arg0))
{
arg0 = bindTempExpr->getSubExpr();
continue;
}
if (auto tempObjExpr = dyn_cast<CXXTemporaryObjectExpr>(arg0))
{
arg0 = tempObjExpr->getArg(0);
continue;
}
if (auto parenExpr = dyn_cast<ParenExpr>(arg0))
{
arg0 = parenExpr->getSubExpr();
continue;
}
argType = arg0->getType();
break;
}
const RecordType* argTemplateType = extractTemplateType(argType);
if (!argTemplateType)
return true;
CXXRecordDecl const* templateParamRD
= dyn_cast<CXXRecordDecl>(compat::getDecl(templateParamType));
CXXRecordDecl const* methodArgRD = dyn_cast<CXXRecordDecl>(compat::getDecl(argTemplateType));
// querying for XInterface (instead of doing an upcast) has special semantics,
// to check for UNO object equivalence.
if (templateParamRD->getName() == "XInterface")
return true;
// XShape is used in UNO aggregates in very "entertaining" ways, which means an UNO_QUERY
// can return a completely different object, e.g. see SwXShape::queryInterface
if (templateParamRD->getName() == "XShape")
return true;
if (mce->getNumArgs() == 2)
if (auto declRefExpr = dyn_cast<DeclRefExpr>(mce->getArg(1)))
{
// no warning expected, used to reject null references
if (auto enumConstantDecl = dyn_cast<EnumConstantDecl>(declRefExpr->getDecl()))
{
if (enumConstantDecl->getName() == "UNO_SET_THROW")
return true;
if (enumConstantDecl->getName() == "UNO_QUERY_THROW")
return true;
if (enumConstantDecl->getName() == "SAL_NO_ACQUIRE")
return true;
}
}
if (methodArgRD->Equals(templateParamRD) || isDerivedFrom(methodArgRD, templateParamRD))
{
report(DiagnosticsEngine::Warning,
"the source reference is already a subtype of the destination reference, just use =",
mce->getBeginLoc())
<< mce->getSourceRange();
}
return true;
}
bool ReferenceCasting::VisitCallExpr(const CallExpr* ce)
{
if (ignoreLocation(ce))
return true;
// don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
StringRef aFileName
= getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(ce->getBeginLoc()));
if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.h"))
return true;
if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.hxx"))
return true;
// look for calls to Reference<T>::query(x)
auto method = dyn_cast_or_null<CXXMethodDecl>(ce->getDirectCallee());
if (!method || !method->getIdentifier() || method->getName() != "query")
return true;
if (ce->getNumArgs() != 1)
return true;
auto methodRecordDecl = dyn_cast<ClassTemplateSpecializationDecl>(method->getParent());
if (!methodRecordDecl || !methodRecordDecl->getIdentifier()
|| methodRecordDecl->getName() != "Reference")
return true;
if (CheckForUnnecessaryGet(ce->getArg(0), /*includeRtlReference*/ true))
report(DiagnosticsEngine::Warning, "unnecessary get() call", ce->getArg(0)->getBeginLoc())
<< ce->getArg(0)->getSourceRange();
// extract the type parameter passed to the template
const RecordType* templateParamType
= dyn_cast<RecordType>(methodRecordDecl->getTemplateArgs()[0].getAsType());
if (!templateParamType)
return true;
// extract the type of the first parameter passed to the method
const Expr* arg0 = ce->getArg(0);
if (!arg0)
return true;
// drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
QualType argType;
for (;;)
{
if (auto castExpr = dyn_cast<CastExpr>(arg0))
{
arg0 = castExpr->getSubExpr();
continue;
}
if (auto matTempExpr = dyn_cast<MaterializeTemporaryExpr>(arg0))
{
arg0 = matTempExpr->getSubExpr();
continue;
}
if (auto bindTempExpr = dyn_cast<CXXBindTemporaryExpr>(arg0))
{
arg0 = bindTempExpr->getSubExpr();
continue;
}
if (auto tempObjExpr = dyn_cast<CXXTemporaryObjectExpr>(arg0))
{
arg0 = tempObjExpr->getArg(0);
continue;
}
if (auto parenExpr = dyn_cast<ParenExpr>(arg0))
{
arg0 = parenExpr->getSubExpr();
continue;
}
argType = arg0->getType();
break;
}
const RecordType* argTemplateType = extractTemplateType(argType);
if (!argTemplateType)
return true;
CXXRecordDecl const* templateParamRD
= dyn_cast<CXXRecordDecl>(compat::getDecl(templateParamType));
CXXRecordDecl const* methodArgRD = dyn_cast<CXXRecordDecl>(compat::getDecl(argTemplateType));
// querying for XInterface (instead of doing an upcast) has special semantics,
// to check for UNO object equivalence.
if (templateParamRD->getName() == "XInterface")
return true;
// XShape is used in UNO aggregates in very "entertaining" ways, which means an UNO_QUERY
// can return a completely different object, e.g. see SwXShape::queryInterface
if (templateParamRD->getName() == "XShape")
return true;
if (methodArgRD->Equals(templateParamRD) || isDerivedFrom(methodArgRD, templateParamRD))
{
report(DiagnosticsEngine::Warning,
"the source reference is already a subtype of the destination reference, just use =",
ce->getBeginLoc())
<< ce->getSourceRange();
}
return true;
}
/**
Check for
Reference<T>(x.get(), UNO_QUERY)
because sometimes simplifying that means the main purpose of this plugin can kick in.
*/
bool ReferenceCasting::CheckForUnnecessaryGet(const Expr* expr, bool includeRtlReference)
{
expr = expr->IgnoreImplicit();
auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(expr);
if (!cxxMemberCallExpr)
return false;
auto methodDecl = cxxMemberCallExpr->getMethodDecl();
if (!methodDecl)
return false;
if (!methodDecl->getIdentifier() || methodDecl->getName() != "get")
return false;
if (!loplugin::TypeCheck(expr->getType()).Pointer())
return false;
auto dc = loplugin::DeclCheck(methodDecl->getParent());
if (dc.Class("Reference").Namespace("uno"))
; // ok
else if (includeRtlReference && dc.Class("Reference").Namespace("rtl"))
; // ok
else
return false;
StringRef aFileName
= getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()));
if (loplugin::isSamePathname(aFileName, SRCDIR "/cppu/qa/test_reference.cxx"))
return false;
return true;
}
static const RecordType* extractTemplateType(QualType cceType)
{
// check for passing raw pointer to interface case
if (cceType->isPointerType())
{
auto pointeeType = cceType->getPointeeType();
#if CLANG_VERSION < 220000
if (auto elaboratedType = dyn_cast<ElaboratedType>(pointeeType))
pointeeType = elaboratedType->desugar();
#endif
if (auto substTemplateTypeParmType = dyn_cast<SubstTemplateTypeParmType>(pointeeType))
pointeeType = substTemplateTypeParmType->desugar();
if (auto recordType = dyn_cast<RecordType>(pointeeType))
return recordType;
}
// extract Foo from Reference<Foo>
if (auto subst = dyn_cast<SubstTemplateTypeParmType>(cceType))
{
if (auto recType = dyn_cast<RecordType>(subst->desugar()))
{
if (auto ctsd = dyn_cast<ClassTemplateSpecializationDecl>(compat::getDecl(recType)))
{
auto const& args = ctsd->getTemplateArgs();
if (args.size() > 0 && args[0].getKind() == TemplateArgument::ArgKind::Type)
return dyn_cast_or_null<RecordType>(args[0].getAsType().getTypePtr());
}
}
}
#if CLANG_VERSION < 220000
if (auto elaboratedType = dyn_cast<ElaboratedType>(cceType))
cceType = elaboratedType->desugar();
#endif
auto cceTST = dyn_cast<TemplateSpecializationType>(cceType);
if (!cceTST)
return NULL;
auto const args = cceTST->template_arguments();
if (args.size() != 1)
return NULL;
const TemplateArgument& cceTA = args[0];
QualType templateParamType = cceTA.getAsType();
#if CLANG_VERSION < 220000
if (auto elaboratedType = dyn_cast<ElaboratedType>(templateParamType))
templateParamType = elaboratedType->desugar();
#endif
return dyn_cast<RecordType>(templateParamType);
}
/**
Implement my own isDerived because we can't always see all the definitions of the classes involved.
which will cause an assert with the normal clang isDerivedFrom code.
*/
static bool isDerivedFrom(const CXXRecordDecl* subtypeRecord, const CXXRecordDecl* baseRecord)
{
// if there is more than one case, then we have an ambiguous conversion, and we can't change the code
// to use the upcasting constructor.
return loplugin::derivedFromCount(subtypeRecord, baseRecord) == 1;
}
loplugin::Plugin::Registration<ReferenceCasting> referencecasting("referencecasting");
} // namespace
#endif // LO_CLANG_SHARED_PLUGINS
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */