/* -*- 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 "config_clang.h" #include /* 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 to a Reference 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 { 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(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 && !isa(cce->getConstructor()->getParamDecl(1)->getType())) 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(constructorArg0)) { constructorArg0 = castExpr->getSubExpr(); continue; } if (auto matTempExpr = dyn_cast(constructorArg0)) { constructorArg0 = matTempExpr->getSubExpr(); continue; } if (auto bindTempExpr = dyn_cast(constructorArg0)) { constructorArg0 = bindTempExpr->getSubExpr(); continue; } if (auto tempObjExpr = dyn_cast(constructorArg0)) { constructorArg0 = tempObjExpr->getArg(0); continue; } if (auto parenExpr = dyn_cast(constructorArg0)) { constructorArg0 = parenExpr->getSubExpr(); continue; } argType = constructorArg0->getType(); break; } const RecordType* argTemplateType = extractTemplateType(argType); if (!argTemplateType) return true; CXXRecordDecl* templateParamRD = dyn_cast(templateParamType->getDecl()); CXXRecordDecl* constructorArgRD = dyn_cast(argTemplateType->getDecl()); // 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(cce->getArg(1))) { // no warning expected, used to reject null references if (auto enumConstantDecl = dyn_cast(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.set(x, UNO_QUERY) constructor auto method = mce->getMethodDecl(); if (!method || !method->getIdentifier() || method->getName() != "set") return true; auto methodRecordDecl = dyn_cast(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(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(arg0)) { arg0 = castExpr->getSubExpr(); continue; } if (auto matTempExpr = dyn_cast(arg0)) { arg0 = matTempExpr->getSubExpr(); continue; } if (auto bindTempExpr = dyn_cast(arg0)) { arg0 = bindTempExpr->getSubExpr(); continue; } if (auto tempObjExpr = dyn_cast(arg0)) { arg0 = tempObjExpr->getArg(0); continue; } if (auto parenExpr = dyn_cast(arg0)) { arg0 = parenExpr->getSubExpr(); continue; } argType = arg0->getType(); break; } const RecordType* argTemplateType = extractTemplateType(argType); if (!argTemplateType) return true; CXXRecordDecl* templateParamRD = dyn_cast(templateParamType->getDecl()); CXXRecordDecl* methodArgRD = dyn_cast(argTemplateType->getDecl()); // 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(mce->getArg(1))) { // no warning expected, used to reject null references if (auto enumConstantDecl = dyn_cast(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::query(x) auto method = dyn_cast_or_null(ce->getDirectCallee()); if (!method || !method->getIdentifier() || method->getName() != "query") return true; if (ce->getNumArgs() != 1) return true; auto methodRecordDecl = dyn_cast(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(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(arg0)) { arg0 = castExpr->getSubExpr(); continue; } if (auto matTempExpr = dyn_cast(arg0)) { arg0 = matTempExpr->getSubExpr(); continue; } if (auto bindTempExpr = dyn_cast(arg0)) { arg0 = bindTempExpr->getSubExpr(); continue; } if (auto tempObjExpr = dyn_cast(arg0)) { arg0 = tempObjExpr->getArg(0); continue; } if (auto parenExpr = dyn_cast(arg0)) { arg0 = parenExpr->getSubExpr(); continue; } argType = arg0->getType(); break; } const RecordType* argTemplateType = extractTemplateType(argType); if (!argTemplateType) return true; CXXRecordDecl* templateParamRD = dyn_cast(templateParamType->getDecl()); CXXRecordDecl* methodArgRD = dyn_cast(argTemplateType->getDecl()); // 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(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(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 (auto elaboratedType = dyn_cast(pointeeType)) pointeeType = elaboratedType->desugar(); if (auto substTemplateTypeParmType = dyn_cast(pointeeType)) pointeeType = substTemplateTypeParmType->desugar(); if (auto recordType = dyn_cast(pointeeType)) return recordType; } // extract Foo from Reference if (auto subst = dyn_cast(cceType)) { if (auto recType = dyn_cast(subst->desugar())) { if (auto ctsd = dyn_cast(recType->getDecl())) { auto const& args = ctsd->getTemplateArgs(); if (args.size() > 0 && args[0].getKind() == TemplateArgument::ArgKind::Type) return dyn_cast_or_null(args[0].getAsType().getTypePtr()); } } } if (auto elaboratedType = dyn_cast(cceType)) cceType = elaboratedType->desugar(); auto cceTST = dyn_cast(cceType); if (!cceTST) return NULL; if (cceTST->getNumArgs() != 1) return NULL; const TemplateArgument& cceTA = cceTST->getArg(0); QualType templateParamType = cceTA.getAsType(); if (auto elaboratedType = dyn_cast(templateParamType)) templateParamType = elaboratedType->desugar(); return dyn_cast(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"); } // namespace #endif // LO_CLANG_SHARED_PLUGINS /* vim:set shiftwidth=4 softtabstop=4 expandtab: */