1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
11 #ifndef LO_CLANG_SHARED_PLUGINS
15 #include "config_clang.h"
19 This is a compile-time checker.
21 Check for cases where we have
22 - two IDL interfaces A and B,
24 - we are converting a Reference<B> to a Reference<A> using UNO_QUERY
26 This makes the code simpler and cheaper, because UNO_QUERY can be surprisingly expensive if used a lot.
32 class ReferenceCasting
: public loplugin::FilteringPlugin
<ReferenceCasting
>
35 explicit ReferenceCasting(loplugin::InstantiationData
const& data
)
36 : FilteringPlugin(data
)
40 bool preRun() override
42 std::string
fn(handler
.getMainFileName());
43 loplugin::normalizeDotDotInFilePath(fn
);
45 if (fn
== SRCDIR
"/dbaccess/source/ui/browser/formadapter.cxx")
48 if (fn
== SRCDIR
"/toolkit/source/controls/stdtabcontroller.cxx")
57 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
61 bool VisitCXXConstructExpr(const CXXConstructExpr
* cce
);
62 bool VisitCXXMemberCallExpr(const CXXMemberCallExpr
* mce
);
63 bool VisitCallExpr(const CallExpr
*);
64 bool VisitInitListExpr(const InitListExpr
*);
67 bool CheckForUnnecessaryGet(const Expr
*, bool includeRtlReference
);
70 static const RecordType
* extractTemplateType(QualType
);
71 static bool isDerivedFrom(const CXXRecordDecl
* subtypeRecord
, const CXXRecordDecl
* baseRecord
);
73 bool ReferenceCasting::VisitInitListExpr(const InitListExpr
* ile
)
75 if (ignoreLocation(ile
))
77 for (const Expr
* expr
: ile
->inits())
79 if (CheckForUnnecessaryGet(expr
, /*includeRtlReference*/ true))
81 report(DiagnosticsEngine::Warning
, "unnecessary get() call", expr
->getBeginLoc())
82 << expr
->getSourceRange();
88 bool ReferenceCasting::VisitCXXConstructExpr(const CXXConstructExpr
* cce
)
90 if (ignoreLocation(cce
))
92 // don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
94 = getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(cce
->getBeginLoc()));
95 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/com/sun/star/uno/Reference.h"))
97 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/com/sun/star/uno/Reference.hxx"))
100 if (cce
->getNumArgs() == 0)
103 // look for calls to the Reference<T>(x, UNO_something) constructor
104 auto constructorClass
= cce
->getConstructor()->getParent();
105 auto dc
= loplugin::DeclCheck(constructorClass
);
106 bool isUnoReference(dc
.Class("Reference").Namespace("uno"));
107 bool isRtlReference(dc
.Class("Reference").Namespace("rtl").GlobalNamespace());
108 if (!isUnoReference
&& !isRtlReference
)
112 if (CheckForUnnecessaryGet(cce
->getArg(0), /*includeRtlReference*/ cce
->getNumArgs() == 1))
114 report(DiagnosticsEngine::Warning
, "unnecessary get() call",
115 cce
->getArg(0)->getBeginLoc())
116 << cce
->getArg(0)->getSourceRange();
119 if (isRtlReference
&& cce
->getNumArgs() == 1)
120 if (CheckForUnnecessaryGet(cce
->getArg(0), /*includeRtlReference*/ true))
122 report(DiagnosticsEngine::Warning
, "unnecessary get() call",
123 cce
->getArg(0)->getBeginLoc())
124 << cce
->getArg(0)->getSourceRange();
130 if (isUnoReference
&& cce
->getNumArgs() != 2)
133 // ignore the up-casting constructor, which has a std::enable_if second parameter
134 if (isUnoReference
&& cce
->getNumArgs() == 2
135 && !cce
->getConstructor()->getParamDecl(1)->getType()->isEnumeralType())
138 // extract the type parameter passed to the template
139 const RecordType
* templateParamType
= extractTemplateType(cce
->getType());
140 if (!templateParamType
)
143 // extract the type of the first parameter passed to the constructor
144 const Expr
* constructorArg0
= cce
->getArg(0);
145 if (!constructorArg0
)
148 // drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
152 if (auto castExpr
= dyn_cast
<CastExpr
>(constructorArg0
))
154 constructorArg0
= castExpr
->getSubExprAsWritten();
157 if (auto matTempExpr
= dyn_cast
<MaterializeTemporaryExpr
>(constructorArg0
))
159 constructorArg0
= matTempExpr
->getSubExpr();
162 if (auto bindTempExpr
= dyn_cast
<CXXBindTemporaryExpr
>(constructorArg0
))
164 constructorArg0
= bindTempExpr
->getSubExpr();
167 if (auto tempObjExpr
= dyn_cast
<CXXTemporaryObjectExpr
>(constructorArg0
))
169 constructorArg0
= tempObjExpr
->getArg(0);
172 if (auto parenExpr
= dyn_cast
<ParenExpr
>(constructorArg0
))
174 constructorArg0
= parenExpr
->getSubExpr();
177 // for the "uno::Reference<X>(*this, UNO_QUERY)" case
178 if (auto unaryOper
= dyn_cast
<UnaryOperator
>(constructorArg0
))
180 if (unaryOper
->getOpcode() == UO_Deref
)
182 constructorArg0
= unaryOper
->getSubExpr();
186 argType
= constructorArg0
->getType();
190 const RecordType
* argTemplateType
= extractTemplateType(argType
);
191 if (!argTemplateType
)
194 CXXRecordDecl
* templateParamRD
= dyn_cast
<CXXRecordDecl
>(templateParamType
->getDecl());
195 CXXRecordDecl
* constructorArgRD
= dyn_cast
<CXXRecordDecl
>(argTemplateType
->getDecl());
197 // querying for XInterface (instead of doing an upcast) has special semantics,
198 // to check for UNO object equivalence.
199 if (templateParamRD
->getName() == "XInterface")
202 // XShape is used in UNO aggregates in very "entertaining" ways, which means an UNO_QUERY
203 // can return a completely different object, e.g. see SwXShape::queryInterface
204 if (templateParamRD
->getName() == "XShape")
207 if (cce
->getNumArgs() == 2)
208 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(cce
->getArg(1)))
210 // no warning expected, used to reject null references
211 if (auto enumConstantDecl
= dyn_cast
<EnumConstantDecl
>(declRefExpr
->getDecl()))
213 if (enumConstantDecl
->getName() == "UNO_SET_THROW")
215 if (enumConstantDecl
->getName() == "UNO_QUERY_THROW")
217 if (enumConstantDecl
->getName() == "SAL_NO_ACQUIRE")
222 if (constructorArgRD
->Equals(templateParamRD
)
223 || isDerivedFrom(constructorArgRD
, templateParamRD
))
225 report(DiagnosticsEngine::Warning
,
226 "the source reference is already a subtype of the destination reference, just use =",
228 << cce
->getSourceRange();
233 bool ReferenceCasting::VisitCXXMemberCallExpr(const CXXMemberCallExpr
* mce
)
235 if (ignoreLocation(mce
))
237 // don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
239 = getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(mce
->getBeginLoc()));
240 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/com/sun/star/uno/Reference.h"))
242 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/com/sun/star/uno/Reference.hxx"))
245 if (mce
->getNumArgs() == 0)
248 // look for calls to the Reference<T>.set(x, UNO_QUERY) constructor
249 auto method
= mce
->getMethodDecl();
250 if (!method
|| !method
->getIdentifier() || method
->getName() != "set")
253 auto methodRecordDecl
= dyn_cast
<ClassTemplateSpecializationDecl
>(mce
->getRecordDecl());
254 if (!methodRecordDecl
|| !methodRecordDecl
->getIdentifier()
255 || methodRecordDecl
->getName() != "Reference")
258 if (CheckForUnnecessaryGet(mce
->getArg(0), /*includeRtlReference*/ mce
->getNumArgs() == 1))
260 report(DiagnosticsEngine::Warning
, "unnecessary get() call", mce
->getArg(0)->getBeginLoc())
261 << mce
->getArg(0)->getSourceRange();
265 if (mce
->getNumArgs() != 2)
268 // extract the type parameter passed to the template
269 const RecordType
* templateParamType
270 = dyn_cast
<RecordType
>(methodRecordDecl
->getTemplateArgs()[0].getAsType());
271 if (!templateParamType
)
274 // extract the type of the first parameter passed to the method
275 const Expr
* arg0
= mce
->getArg(0);
279 // drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
283 if (auto castExpr
= dyn_cast
<CastExpr
>(arg0
))
285 arg0
= castExpr
->getSubExpr();
288 if (auto matTempExpr
= dyn_cast
<MaterializeTemporaryExpr
>(arg0
))
290 arg0
= matTempExpr
->getSubExpr();
293 if (auto bindTempExpr
= dyn_cast
<CXXBindTemporaryExpr
>(arg0
))
295 arg0
= bindTempExpr
->getSubExpr();
298 if (auto tempObjExpr
= dyn_cast
<CXXTemporaryObjectExpr
>(arg0
))
300 arg0
= tempObjExpr
->getArg(0);
303 if (auto parenExpr
= dyn_cast
<ParenExpr
>(arg0
))
305 arg0
= parenExpr
->getSubExpr();
308 argType
= arg0
->getType();
312 const RecordType
* argTemplateType
= extractTemplateType(argType
);
313 if (!argTemplateType
)
316 CXXRecordDecl
* templateParamRD
= dyn_cast
<CXXRecordDecl
>(templateParamType
->getDecl());
317 CXXRecordDecl
* methodArgRD
= dyn_cast
<CXXRecordDecl
>(argTemplateType
->getDecl());
319 // querying for XInterface (instead of doing an upcast) has special semantics,
320 // to check for UNO object equivalence.
321 if (templateParamRD
->getName() == "XInterface")
324 // XShape is used in UNO aggregates in very "entertaining" ways, which means an UNO_QUERY
325 // can return a completely different object, e.g. see SwXShape::queryInterface
326 if (templateParamRD
->getName() == "XShape")
329 if (mce
->getNumArgs() == 2)
330 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(mce
->getArg(1)))
332 // no warning expected, used to reject null references
333 if (auto enumConstantDecl
= dyn_cast
<EnumConstantDecl
>(declRefExpr
->getDecl()))
335 if (enumConstantDecl
->getName() == "UNO_SET_THROW")
337 if (enumConstantDecl
->getName() == "UNO_QUERY_THROW")
339 if (enumConstantDecl
->getName() == "SAL_NO_ACQUIRE")
344 if (methodArgRD
->Equals(templateParamRD
) || isDerivedFrom(methodArgRD
, templateParamRD
))
346 report(DiagnosticsEngine::Warning
,
347 "the source reference is already a subtype of the destination reference, just use =",
349 << mce
->getSourceRange();
354 bool ReferenceCasting::VisitCallExpr(const CallExpr
* ce
)
356 if (ignoreLocation(ce
))
358 // don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
360 = getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(ce
->getBeginLoc()));
361 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/com/sun/star/uno/Reference.h"))
363 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/com/sun/star/uno/Reference.hxx"))
366 // look for calls to Reference<T>::query(x)
367 auto method
= dyn_cast_or_null
<CXXMethodDecl
>(ce
->getDirectCallee());
368 if (!method
|| !method
->getIdentifier() || method
->getName() != "query")
370 if (ce
->getNumArgs() != 1)
373 auto methodRecordDecl
= dyn_cast
<ClassTemplateSpecializationDecl
>(method
->getParent());
374 if (!methodRecordDecl
|| !methodRecordDecl
->getIdentifier()
375 || methodRecordDecl
->getName() != "Reference")
378 if (CheckForUnnecessaryGet(ce
->getArg(0), /*includeRtlReference*/ true))
379 report(DiagnosticsEngine::Warning
, "unnecessary get() call", ce
->getArg(0)->getBeginLoc())
380 << ce
->getArg(0)->getSourceRange();
382 // extract the type parameter passed to the template
383 const RecordType
* templateParamType
384 = dyn_cast
<RecordType
>(methodRecordDecl
->getTemplateArgs()[0].getAsType());
385 if (!templateParamType
)
388 // extract the type of the first parameter passed to the method
389 const Expr
* arg0
= ce
->getArg(0);
393 // drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
397 if (auto castExpr
= dyn_cast
<CastExpr
>(arg0
))
399 arg0
= castExpr
->getSubExpr();
402 if (auto matTempExpr
= dyn_cast
<MaterializeTemporaryExpr
>(arg0
))
404 arg0
= matTempExpr
->getSubExpr();
407 if (auto bindTempExpr
= dyn_cast
<CXXBindTemporaryExpr
>(arg0
))
409 arg0
= bindTempExpr
->getSubExpr();
412 if (auto tempObjExpr
= dyn_cast
<CXXTemporaryObjectExpr
>(arg0
))
414 arg0
= tempObjExpr
->getArg(0);
417 if (auto parenExpr
= dyn_cast
<ParenExpr
>(arg0
))
419 arg0
= parenExpr
->getSubExpr();
422 argType
= arg0
->getType();
426 const RecordType
* argTemplateType
= extractTemplateType(argType
);
427 if (!argTemplateType
)
430 CXXRecordDecl
* templateParamRD
= dyn_cast
<CXXRecordDecl
>(templateParamType
->getDecl());
431 CXXRecordDecl
* methodArgRD
= dyn_cast
<CXXRecordDecl
>(argTemplateType
->getDecl());
433 // querying for XInterface (instead of doing an upcast) has special semantics,
434 // to check for UNO object equivalence.
435 if (templateParamRD
->getName() == "XInterface")
438 // XShape is used in UNO aggregates in very "entertaining" ways, which means an UNO_QUERY
439 // can return a completely different object, e.g. see SwXShape::queryInterface
440 if (templateParamRD
->getName() == "XShape")
443 if (methodArgRD
->Equals(templateParamRD
) || isDerivedFrom(methodArgRD
, templateParamRD
))
445 report(DiagnosticsEngine::Warning
,
446 "the source reference is already a subtype of the destination reference, just use =",
448 << ce
->getSourceRange();
455 Reference<T>(x.get(), UNO_QUERY)
456 because sometimes simplifying that means the main purpose of this plugin can kick in.
458 bool ReferenceCasting::CheckForUnnecessaryGet(const Expr
* expr
, bool includeRtlReference
)
460 expr
= expr
->IgnoreImplicit();
461 auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(expr
);
462 if (!cxxMemberCallExpr
)
464 auto methodDecl
= cxxMemberCallExpr
->getMethodDecl();
467 if (!methodDecl
->getIdentifier() || methodDecl
->getName() != "get")
470 if (!loplugin::TypeCheck(expr
->getType()).Pointer())
472 auto dc
= loplugin::DeclCheck(methodDecl
->getParent());
473 if (dc
.Class("Reference").Namespace("uno"))
475 else if (includeRtlReference
&& dc
.Class("Reference").Namespace("rtl"))
481 = getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(expr
->getBeginLoc()));
482 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/cppu/qa/test_reference.cxx"))
488 static const RecordType
* extractTemplateType(QualType cceType
)
490 // check for passing raw pointer to interface case
491 if (cceType
->isPointerType())
493 auto pointeeType
= cceType
->getPointeeType();
494 if (auto elaboratedType
= dyn_cast
<ElaboratedType
>(pointeeType
))
495 pointeeType
= elaboratedType
->desugar();
496 if (auto substTemplateTypeParmType
= dyn_cast
<SubstTemplateTypeParmType
>(pointeeType
))
497 pointeeType
= substTemplateTypeParmType
->desugar();
498 if (auto recordType
= dyn_cast
<RecordType
>(pointeeType
))
502 // extract Foo from Reference<Foo>
503 if (auto subst
= dyn_cast
<SubstTemplateTypeParmType
>(cceType
))
505 if (auto recType
= dyn_cast
<RecordType
>(subst
->desugar()))
507 if (auto ctsd
= dyn_cast
<ClassTemplateSpecializationDecl
>(recType
->getDecl()))
509 auto const& args
= ctsd
->getTemplateArgs();
510 if (args
.size() > 0 && args
[0].getKind() == TemplateArgument::ArgKind::Type
)
511 return dyn_cast_or_null
<RecordType
>(args
[0].getAsType().getTypePtr());
516 if (auto elaboratedType
= dyn_cast
<ElaboratedType
>(cceType
))
517 cceType
= elaboratedType
->desugar();
518 auto cceTST
= dyn_cast
<TemplateSpecializationType
>(cceType
);
521 auto const args
= cceTST
->template_arguments();
522 if (args
.size() != 1)
524 const TemplateArgument
& cceTA
= args
[0];
525 QualType templateParamType
= cceTA
.getAsType();
526 if (auto elaboratedType
= dyn_cast
<ElaboratedType
>(templateParamType
))
527 templateParamType
= elaboratedType
->desugar();
528 return dyn_cast
<RecordType
>(templateParamType
);
532 Implement my own isDerived because we can't always see all the definitions of the classes involved.
533 which will cause an assert with the normal clang isDerivedFrom code.
535 static bool isDerivedFrom(const CXXRecordDecl
* subtypeRecord
, const CXXRecordDecl
* baseRecord
)
537 // if there is more than one case, then we have an ambiguous conversion, and we can't change the code
538 // to use the upcasting constructor.
539 return loplugin::derivedFromCount(subtypeRecord
, baseRecord
) == 1;
542 loplugin::Plugin::Registration
<ReferenceCasting
> referencecasting("referencecasting");
546 #endif // LO_CLANG_SHARED_PLUGINS
548 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */