bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / referencecasting.cxx
blob45b65d3e7f26a1b6816ebb07fab86d3aa871ad3d
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 <iostream>
18 This is a compile-time checker.
20 Check for cases where we have
21 - two IDL interfaces A and B,
22 - B extends A
23 - we are converting a Reference<B> to a Reference<A> using UNO_QUERY
25 This makes the code simpler and cheaper, because UNO_QUERY can be surprisingly expensive if used a lot.
29 namespace
31 class ReferenceCasting : public loplugin::FilteringPlugin<ReferenceCasting>
33 public:
34 explicit ReferenceCasting(loplugin::InstantiationData const& data)
35 : FilteringPlugin(data)
39 bool preRun() override
41 std::string fn(handler.getMainFileName());
42 loplugin::normalizeDotDotInFilePath(fn);
43 // macros
44 if (fn == SRCDIR "/dbaccess/source/ui/browser/formadapter.cxx")
45 return false;
46 return true;
49 void run() override
51 if (preRun())
53 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
57 bool VisitCXXConstructExpr(const CXXConstructExpr* cce);
58 bool VisitCXXMemberCallExpr(const CXXMemberCallExpr* mce);
60 private:
61 bool CheckForUnnecessaryGet(const Expr*);
64 static const RecordType* extractTemplateType(const clang::Type*);
65 static bool isDerivedFrom(const CXXRecordDecl* subtypeRecord, const CXXRecordDecl* baseRecord);
67 bool ReferenceCasting::VisitCXXConstructExpr(const CXXConstructExpr* cce)
69 // don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
70 StringRef aFileName = getFilenameOfLocation(
71 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(cce)));
72 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.h"))
73 return true;
74 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.hxx"))
75 return true;
77 // look for calls to the Reference<T>(x, UNO_something) constructor
78 auto constructorClass = cce->getConstructor()->getParent();
79 if (!constructorClass->getIdentifier() || constructorClass->getName() != "Reference")
80 return true;
82 if (cce->getNumArgs() != 2)
83 return true;
85 if (CheckForUnnecessaryGet(cce->getArg(0)))
86 report(DiagnosticsEngine::Warning, "unnecessary get() call", compat::getBeginLoc(cce))
87 << cce->getSourceRange();
89 // ignore the up-casting constructor
90 if (!isa<EnumType>(cce->getConstructor()->getParamDecl(1)->getType()))
91 return true;
93 // extract the type parameter passed to the template
94 const RecordType* templateParamType = extractTemplateType(cce->getType().getTypePtr());
95 if (!templateParamType)
96 return true;
98 // extract the type of the first parameter passed to the constructor
99 const Expr* constructorArg0 = cce->getArg(0);
100 if (!constructorArg0)
101 return true;
103 // drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
104 const clang::Type* argType;
105 for (;;)
107 if (auto castExpr = dyn_cast<CastExpr>(constructorArg0))
109 constructorArg0 = castExpr->getSubExpr();
110 continue;
112 if (auto matTempExpr = dyn_cast<MaterializeTemporaryExpr>(constructorArg0))
114 constructorArg0 = matTempExpr->GetTemporaryExpr();
115 continue;
117 if (auto bindTempExpr = dyn_cast<CXXBindTemporaryExpr>(constructorArg0))
119 constructorArg0 = bindTempExpr->getSubExpr();
120 continue;
122 if (auto tempObjExpr = dyn_cast<CXXTemporaryObjectExpr>(constructorArg0))
124 constructorArg0 = tempObjExpr->getArg(0);
125 continue;
127 if (auto parenExpr = dyn_cast<ParenExpr>(constructorArg0))
129 constructorArg0 = parenExpr->getSubExpr();
130 continue;
132 argType = constructorArg0->getType().getTypePtr();
133 break;
136 const RecordType* argTemplateType = extractTemplateType(argType);
137 if (!argTemplateType)
138 return true;
140 CXXRecordDecl* templateParamRD = dyn_cast<CXXRecordDecl>(templateParamType->getDecl());
141 CXXRecordDecl* constructorArgRD = dyn_cast<CXXRecordDecl>(argTemplateType->getDecl());
143 // querying for XInterface (instead of doing an upcast) has special semantics,
144 // to check for UNO object equivalence.
145 if (templateParamRD->getName() == "XInterface")
146 return true;
148 // XShape is used in UNO aggregates in very "entertaining" ways, which means an UNO_QUERY
149 // can return a completely different object, e.g. see SwXShape::queryInterface
150 if (templateParamRD->getName() == "XShape")
151 return true;
153 if (auto declRefExpr = dyn_cast<DeclRefExpr>(cce->getArg(1)))
155 // no warning expected, used to reject null references
156 if (auto enumConstantDecl = dyn_cast<EnumConstantDecl>(declRefExpr->getDecl()))
158 if (enumConstantDecl->getName() == "UNO_SET_THROW")
159 return true;
160 if (enumConstantDecl->getName() == "UNO_QUERY_THROW")
161 return true;
162 if (enumConstantDecl->getName() == "SAL_NO_ACQUIRE")
163 return true;
167 if (constructorArgRD->Equals(templateParamRD)
168 || isDerivedFrom(constructorArgRD, templateParamRD))
170 report(DiagnosticsEngine::Warning,
171 "the source reference is already a subtype of the destination reference, just use =",
172 compat::getBeginLoc(cce))
173 << cce->getSourceRange();
175 return true;
178 bool ReferenceCasting::VisitCXXMemberCallExpr(const CXXMemberCallExpr* mce)
180 // don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
181 StringRef aFileName = getFilenameOfLocation(
182 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(mce)));
183 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.h"))
184 return true;
185 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.hxx"))
186 return true;
188 // look for calls to the Reference<T>.set(x, UNO_QUERY) constructor
189 auto method = mce->getMethodDecl();
190 if (!method || !method->getIdentifier() || method->getName() != "set")
191 return true;
192 if (mce->getNumArgs() != 2)
193 return true;
195 auto methodRecordDecl = dyn_cast<ClassTemplateSpecializationDecl>(mce->getRecordDecl());
196 if (!methodRecordDecl || !methodRecordDecl->getIdentifier()
197 || methodRecordDecl->getName() != "Reference")
198 return true;
200 if (CheckForUnnecessaryGet(mce->getArg(0)))
201 report(DiagnosticsEngine::Warning, "unnecessary get() call", compat::getBeginLoc(mce))
202 << mce->getSourceRange();
204 // extract the type parameter passed to the template
205 const RecordType* templateParamType
206 = dyn_cast<RecordType>(methodRecordDecl->getTemplateArgs()[0].getAsType());
207 if (!templateParamType)
208 return true;
210 // extract the type of the first parameter passed to the method
211 const Expr* arg0 = mce->getArg(0);
212 if (!arg0)
213 return true;
215 // drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
216 const clang::Type* argType;
217 for (;;)
219 if (auto castExpr = dyn_cast<CastExpr>(arg0))
221 arg0 = castExpr->getSubExpr();
222 continue;
224 if (auto matTempExpr = dyn_cast<MaterializeTemporaryExpr>(arg0))
226 arg0 = matTempExpr->GetTemporaryExpr();
227 continue;
229 if (auto bindTempExpr = dyn_cast<CXXBindTemporaryExpr>(arg0))
231 arg0 = bindTempExpr->getSubExpr();
232 continue;
234 if (auto tempObjExpr = dyn_cast<CXXTemporaryObjectExpr>(arg0))
236 arg0 = tempObjExpr->getArg(0);
237 continue;
239 if (auto parenExpr = dyn_cast<ParenExpr>(arg0))
241 arg0 = parenExpr->getSubExpr();
242 continue;
244 argType = arg0->getType().getTypePtr();
245 break;
248 const RecordType* argTemplateType = extractTemplateType(argType);
249 if (!argTemplateType)
250 return true;
252 CXXRecordDecl* templateParamRD = dyn_cast<CXXRecordDecl>(templateParamType->getDecl());
253 CXXRecordDecl* methodArgRD = dyn_cast<CXXRecordDecl>(argTemplateType->getDecl());
255 // querying for XInterface (instead of doing an upcast) has special semantics,
256 // to check for UNO object equivalence.
257 if (templateParamRD->getName() == "XInterface")
258 return true;
260 // XShape is used in UNO aggregates in very "entertaining" ways, which means an UNO_QUERY
261 // can return a completely different object, e.g. see SwXShape::queryInterface
262 if (templateParamRD->getName() == "XShape")
263 return true;
265 if (auto declRefExpr = dyn_cast<DeclRefExpr>(mce->getArg(1)))
267 // no warning expected, used to reject null references
268 if (auto enumConstantDecl = dyn_cast<EnumConstantDecl>(declRefExpr->getDecl()))
270 if (enumConstantDecl->getName() == "UNO_SET_THROW")
271 return true;
272 if (enumConstantDecl->getName() == "UNO_QUERY_THROW")
273 return true;
274 if (enumConstantDecl->getName() == "SAL_NO_ACQUIRE")
275 return true;
279 if (methodArgRD->Equals(templateParamRD) || isDerivedFrom(methodArgRD, templateParamRD))
281 report(DiagnosticsEngine::Warning,
282 "the source reference is already a subtype of the destination reference, just use =",
283 compat::getBeginLoc(mce))
284 << mce->getSourceRange();
286 return true;
290 Check for
291 Reference<T>(x.get(), UNO_QUERY)
292 because sometimes simplifying that means the main purpose of this plugin can kick in.
294 bool ReferenceCasting::CheckForUnnecessaryGet(const Expr* expr)
296 expr = expr->IgnoreImplicit();
297 auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(expr);
298 if (!cxxMemberCallExpr)
299 return false;
300 auto methodDecl = cxxMemberCallExpr->getMethodDecl();
301 if (!methodDecl)
302 return false;
303 if (!methodDecl->getIdentifier() || methodDecl->getName() != "get")
304 return false;
306 if (!loplugin::TypeCheck(expr->getType()).Pointer())
307 return false;
308 if (!loplugin::DeclCheck(methodDecl->getParent()).Class("Reference").Namespace("uno"))
309 return false;
311 StringRef aFileName = getFilenameOfLocation(
312 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr)));
313 if (loplugin::isSamePathname(aFileName, SRCDIR "/cppu/qa/test_reference.cxx"))
314 return false;
316 return true;
319 static const RecordType* extractTemplateType(const clang::Type* cceType)
321 // check for passing raw pointer to interface case
322 if (cceType->isPointerType())
324 auto pointeeType = cceType->getPointeeType();
325 if (auto elaboratedType = dyn_cast<ElaboratedType>(pointeeType))
326 pointeeType = elaboratedType->desugar();
327 if (auto recordType = dyn_cast<RecordType>(pointeeType))
328 return recordType;
331 if (auto elaboratedType = dyn_cast<ElaboratedType>(cceType))
332 cceType = elaboratedType->desugar().getTypePtr();
333 auto cceTST = dyn_cast<TemplateSpecializationType>(cceType);
334 if (!cceTST)
335 return NULL;
336 if (cceTST->getNumArgs() != 1)
337 return NULL;
338 const TemplateArgument& cceTA = cceTST->getArg(0);
339 const clang::Type* templateParamType = cceTA.getAsType().getTypePtr();
340 if (auto elaboratedType = dyn_cast<ElaboratedType>(templateParamType))
341 templateParamType = elaboratedType->desugar().getTypePtr();
342 return dyn_cast<RecordType>(templateParamType);
346 Implement my own isDerived because we can't always see all the definitions of the classes involved.
347 which will cause an assert with the normal clang isDerivedFrom code.
349 static bool isDerivedFrom(const CXXRecordDecl* subtypeRecord, const CXXRecordDecl* baseRecord)
351 // if there is more than one case, then we have an ambiguous conversion, and we can't change the code
352 // to use the upcasting constructor.
353 return loplugin::derivedFromCount(subtypeRecord, baseRecord) == 1;
356 loplugin::Plugin::Registration<ReferenceCasting> referencecasting("referencecasting");
358 } // namespace
360 #endif // LO_CLANG_SHARED_PLUGINS
362 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */