null-deref seen in testing
[LibreOffice.git] / compilerplugins / clang / referencecasting.cxx
blobcff5b050ea9faa9c32b66b5e744a6af8664787f7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * Based on LLVM/Clang.
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
13 #include "plugin.hxx"
14 #include "check.hxx"
15 #include "config_clang.h"
16 #include <iostream>
19 This is a compile-time checker.
21 Check for cases where we have
22 - two IDL interfaces A and B,
23 - B extends A
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.
30 namespace
32 class ReferenceCasting : public loplugin::FilteringPlugin<ReferenceCasting>
34 public:
35 explicit ReferenceCasting(loplugin::InstantiationData const& data)
36 : FilteringPlugin(data)
40 bool preRun() override
42 std::string fn(handler.getMainFileName());
43 loplugin::normalizeDotDotInFilePath(fn);
44 // macros
45 if (fn == SRCDIR "/dbaccess/source/ui/browser/formadapter.cxx")
46 return false;
47 // UNO aggregation
48 if (fn == SRCDIR "/toolkit/source/controls/stdtabcontroller.cxx")
49 return false;
50 return true;
53 void run() override
55 if (preRun())
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*);
66 private:
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))
76 return true;
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();
83 return true;
86 return true;
88 bool ReferenceCasting::VisitCXXConstructExpr(const CXXConstructExpr* cce)
90 if (ignoreLocation(cce))
91 return true;
92 // don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
93 StringRef aFileName
94 = getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(cce->getBeginLoc()));
95 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.h"))
96 return true;
97 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.hxx"))
98 return true;
100 if (cce->getNumArgs() == 0)
101 return true;
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)
109 return true;
111 if (isUnoReference)
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();
117 return true;
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();
125 return true;
128 if (isRtlReference)
129 return true;
130 if (isUnoReference && cce->getNumArgs() != 2)
131 return true;
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())
136 return true;
138 // extract the type parameter passed to the template
139 const RecordType* templateParamType = extractTemplateType(cce->getType());
140 if (!templateParamType)
141 return true;
143 // extract the type of the first parameter passed to the constructor
144 const Expr* constructorArg0 = cce->getArg(0);
145 if (!constructorArg0)
146 return true;
148 // drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
149 QualType argType;
150 for (;;)
152 if (auto castExpr = dyn_cast<CastExpr>(constructorArg0))
154 constructorArg0 = castExpr->getSubExprAsWritten();
155 continue;
157 if (auto matTempExpr = dyn_cast<MaterializeTemporaryExpr>(constructorArg0))
159 constructorArg0 = matTempExpr->getSubExpr();
160 continue;
162 if (auto bindTempExpr = dyn_cast<CXXBindTemporaryExpr>(constructorArg0))
164 constructorArg0 = bindTempExpr->getSubExpr();
165 continue;
167 if (auto tempObjExpr = dyn_cast<CXXTemporaryObjectExpr>(constructorArg0))
169 constructorArg0 = tempObjExpr->getArg(0);
170 continue;
172 if (auto parenExpr = dyn_cast<ParenExpr>(constructorArg0))
174 constructorArg0 = parenExpr->getSubExpr();
175 continue;
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();
183 continue;
186 argType = constructorArg0->getType();
187 break;
190 const RecordType* argTemplateType = extractTemplateType(argType);
191 if (!argTemplateType)
192 return true;
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")
200 return true;
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")
205 return true;
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")
214 return true;
215 if (enumConstantDecl->getName() == "UNO_QUERY_THROW")
216 return true;
217 if (enumConstantDecl->getName() == "SAL_NO_ACQUIRE")
218 return true;
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 =",
227 cce->getBeginLoc())
228 << cce->getSourceRange();
230 return true;
233 bool ReferenceCasting::VisitCXXMemberCallExpr(const CXXMemberCallExpr* mce)
235 if (ignoreLocation(mce))
236 return true;
237 // don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
238 StringRef aFileName
239 = getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(mce->getBeginLoc()));
240 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.h"))
241 return true;
242 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.hxx"))
243 return true;
245 if (mce->getNumArgs() == 0)
246 return true;
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")
251 return true;
253 auto methodRecordDecl = dyn_cast<ClassTemplateSpecializationDecl>(mce->getRecordDecl());
254 if (!methodRecordDecl || !methodRecordDecl->getIdentifier()
255 || methodRecordDecl->getName() != "Reference")
256 return true;
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();
262 return true;
265 if (mce->getNumArgs() != 2)
266 return true;
268 // extract the type parameter passed to the template
269 const RecordType* templateParamType
270 = dyn_cast<RecordType>(methodRecordDecl->getTemplateArgs()[0].getAsType());
271 if (!templateParamType)
272 return true;
274 // extract the type of the first parameter passed to the method
275 const Expr* arg0 = mce->getArg(0);
276 if (!arg0)
277 return true;
279 // drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
280 QualType argType;
281 for (;;)
283 if (auto castExpr = dyn_cast<CastExpr>(arg0))
285 arg0 = castExpr->getSubExpr();
286 continue;
288 if (auto matTempExpr = dyn_cast<MaterializeTemporaryExpr>(arg0))
290 arg0 = matTempExpr->getSubExpr();
291 continue;
293 if (auto bindTempExpr = dyn_cast<CXXBindTemporaryExpr>(arg0))
295 arg0 = bindTempExpr->getSubExpr();
296 continue;
298 if (auto tempObjExpr = dyn_cast<CXXTemporaryObjectExpr>(arg0))
300 arg0 = tempObjExpr->getArg(0);
301 continue;
303 if (auto parenExpr = dyn_cast<ParenExpr>(arg0))
305 arg0 = parenExpr->getSubExpr();
306 continue;
308 argType = arg0->getType();
309 break;
312 const RecordType* argTemplateType = extractTemplateType(argType);
313 if (!argTemplateType)
314 return true;
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")
322 return true;
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")
327 return true;
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")
336 return true;
337 if (enumConstantDecl->getName() == "UNO_QUERY_THROW")
338 return true;
339 if (enumConstantDecl->getName() == "SAL_NO_ACQUIRE")
340 return true;
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 =",
348 mce->getBeginLoc())
349 << mce->getSourceRange();
351 return true;
354 bool ReferenceCasting::VisitCallExpr(const CallExpr* ce)
356 if (ignoreLocation(ce))
357 return true;
358 // don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
359 StringRef aFileName
360 = getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(ce->getBeginLoc()));
361 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.h"))
362 return true;
363 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.hxx"))
364 return true;
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")
369 return true;
370 if (ce->getNumArgs() != 1)
371 return true;
373 auto methodRecordDecl = dyn_cast<ClassTemplateSpecializationDecl>(method->getParent());
374 if (!methodRecordDecl || !methodRecordDecl->getIdentifier()
375 || methodRecordDecl->getName() != "Reference")
376 return true;
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)
386 return true;
388 // extract the type of the first parameter passed to the method
389 const Expr* arg0 = ce->getArg(0);
390 if (!arg0)
391 return true;
393 // drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
394 QualType argType;
395 for (;;)
397 if (auto castExpr = dyn_cast<CastExpr>(arg0))
399 arg0 = castExpr->getSubExpr();
400 continue;
402 if (auto matTempExpr = dyn_cast<MaterializeTemporaryExpr>(arg0))
404 arg0 = matTempExpr->getSubExpr();
405 continue;
407 if (auto bindTempExpr = dyn_cast<CXXBindTemporaryExpr>(arg0))
409 arg0 = bindTempExpr->getSubExpr();
410 continue;
412 if (auto tempObjExpr = dyn_cast<CXXTemporaryObjectExpr>(arg0))
414 arg0 = tempObjExpr->getArg(0);
415 continue;
417 if (auto parenExpr = dyn_cast<ParenExpr>(arg0))
419 arg0 = parenExpr->getSubExpr();
420 continue;
422 argType = arg0->getType();
423 break;
426 const RecordType* argTemplateType = extractTemplateType(argType);
427 if (!argTemplateType)
428 return true;
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")
436 return true;
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")
441 return true;
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 =",
447 ce->getBeginLoc())
448 << ce->getSourceRange();
450 return true;
454 Check for
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)
463 return false;
464 auto methodDecl = cxxMemberCallExpr->getMethodDecl();
465 if (!methodDecl)
466 return false;
467 if (!methodDecl->getIdentifier() || methodDecl->getName() != "get")
468 return false;
470 if (!loplugin::TypeCheck(expr->getType()).Pointer())
471 return false;
472 auto dc = loplugin::DeclCheck(methodDecl->getParent());
473 if (dc.Class("Reference").Namespace("uno"))
474 ; // ok
475 else if (includeRtlReference && dc.Class("Reference").Namespace("rtl"))
476 ; // ok
477 else
478 return false;
480 StringRef aFileName
481 = getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()));
482 if (loplugin::isSamePathname(aFileName, SRCDIR "/cppu/qa/test_reference.cxx"))
483 return false;
485 return true;
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))
499 return recordType;
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);
519 if (!cceTST)
520 return NULL;
521 auto const args = cceTST->template_arguments();
522 if (args.size() != 1)
523 return NULL;
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");
544 } // namespace
546 #endif // LO_CLANG_SHARED_PLUGINS
548 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */