1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * Based on LLVM/Clang.
6 *
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
9 *
10 */
11 #ifndef LO_CLANG_SHARED_PLUGINS
12
13 #include "plugin.hxx"
14 #include "check.hxx"
15 #include <iostream>
16
17 /*
18 This is a compile-time checker.
19
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
24
25 This makes the code simpler and cheaper, because UNO_QUERY can be surprisingly expensive if used a lot.
26
27 */
28
29 namespace
30 {
31 class ReferenceCasting : public loplugin::FilteringPlugin<ReferenceCasting>
32 {
33 public:
ReferenceCasting(loplugin::InstantiationData const & data)34 explicit ReferenceCasting(loplugin::InstantiationData const& data)
35 : FilteringPlugin(data)
36 {
37 }
38
preRun()39 bool preRun() override
40 {
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 // UNO aggregation
47 if (fn == SRCDIR "/toolkit/source/controls/stdtabcontroller.cxx")
48 return false;
49 return true;
50 }
51
run()52 void run() override
53 {
54 if (preRun())
55 {
56 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
57 }
58 }
59
60 bool VisitCXXConstructExpr(const CXXConstructExpr* cce);
61 bool VisitCXXMemberCallExpr(const CXXMemberCallExpr* mce);
62
63 private:
64 bool CheckForUnnecessaryGet(const Expr*);
65 };
66
67 static const RecordType* extractTemplateType(const clang::Type*);
68 static bool isDerivedFrom(const CXXRecordDecl* subtypeRecord, const CXXRecordDecl* baseRecord);
69
VisitCXXConstructExpr(const CXXConstructExpr * cce)70 bool ReferenceCasting::VisitCXXConstructExpr(const CXXConstructExpr* cce)
71 {
72 // don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
73 StringRef aFileName = getFilenameOfLocation(
74 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(cce)));
75 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.h"))
76 return true;
77 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.hxx"))
78 return true;
79
80 // look for calls to the Reference<T>(x, UNO_something) constructor
81 auto constructorClass = cce->getConstructor()->getParent();
82 if (!constructorClass->getIdentifier() || constructorClass->getName() != "Reference")
83 return true;
84
85 if (cce->getNumArgs() != 2)
86 return true;
87
88 if (CheckForUnnecessaryGet(cce->getArg(0)))
89 report(DiagnosticsEngine::Warning, "unnecessary get() call", compat::getBeginLoc(cce))
90 << cce->getSourceRange();
91
92 // ignore the up-casting constructor
93 if (!isa<EnumType>(cce->getConstructor()->getParamDecl(1)->getType()))
94 return true;
95
96 // extract the type parameter passed to the template
97 const RecordType* templateParamType = extractTemplateType(cce->getType().getTypePtr());
98 if (!templateParamType)
99 return true;
100
101 // extract the type of the first parameter passed to the constructor
102 const Expr* constructorArg0 = cce->getArg(0);
103 if (!constructorArg0)
104 return true;
105
106 // drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
107 const clang::Type* argType;
108 for (;;)
109 {
110 if (auto castExpr = dyn_cast<CastExpr>(constructorArg0))
111 {
112 constructorArg0 = castExpr->getSubExpr();
113 continue;
114 }
115 if (auto matTempExpr = dyn_cast<MaterializeTemporaryExpr>(constructorArg0))
116 {
117 constructorArg0 = matTempExpr->GetTemporaryExpr();
118 continue;
119 }
120 if (auto bindTempExpr = dyn_cast<CXXBindTemporaryExpr>(constructorArg0))
121 {
122 constructorArg0 = bindTempExpr->getSubExpr();
123 continue;
124 }
125 if (auto tempObjExpr = dyn_cast<CXXTemporaryObjectExpr>(constructorArg0))
126 {
127 constructorArg0 = tempObjExpr->getArg(0);
128 continue;
129 }
130 if (auto parenExpr = dyn_cast<ParenExpr>(constructorArg0))
131 {
132 constructorArg0 = parenExpr->getSubExpr();
133 continue;
134 }
135 argType = constructorArg0->getType().getTypePtr();
136 break;
137 }
138
139 const RecordType* argTemplateType = extractTemplateType(argType);
140 if (!argTemplateType)
141 return true;
142
143 CXXRecordDecl* templateParamRD = dyn_cast<CXXRecordDecl>(templateParamType->getDecl());
144 CXXRecordDecl* constructorArgRD = dyn_cast<CXXRecordDecl>(argTemplateType->getDecl());
145
146 // querying for XInterface (instead of doing an upcast) has special semantics,
147 // to check for UNO object equivalence.
148 if (templateParamRD->getName() == "XInterface")
149 return true;
150
151 // XShape is used in UNO aggregates in very "entertaining" ways, which means an UNO_QUERY
152 // can return a completely different object, e.g. see SwXShape::queryInterface
153 if (templateParamRD->getName() == "XShape")
154 return true;
155
156 if (auto declRefExpr = dyn_cast<DeclRefExpr>(cce->getArg(1)))
157 {
158 // no warning expected, used to reject null references
159 if (auto enumConstantDecl = dyn_cast<EnumConstantDecl>(declRefExpr->getDecl()))
160 {
161 if (enumConstantDecl->getName() == "UNO_SET_THROW")
162 return true;
163 if (enumConstantDecl->getName() == "UNO_QUERY_THROW")
164 return true;
165 if (enumConstantDecl->getName() == "SAL_NO_ACQUIRE")
166 return true;
167 }
168 }
169
170 if (constructorArgRD->Equals(templateParamRD)
171 || isDerivedFrom(constructorArgRD, templateParamRD))
172 {
173 report(DiagnosticsEngine::Warning,
174 "the source reference is already a subtype of the destination reference, just use =",
175 compat::getBeginLoc(cce))
176 << cce->getSourceRange();
177 }
178 return true;
179 }
180
VisitCXXMemberCallExpr(const CXXMemberCallExpr * mce)181 bool ReferenceCasting::VisitCXXMemberCallExpr(const CXXMemberCallExpr* mce)
182 {
183 // don't bother processing anything in the Reference.h file. Makes my life easier when debugging this.
184 StringRef aFileName = getFilenameOfLocation(
185 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(mce)));
186 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.h"))
187 return true;
188 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/com/sun/star/uno/Reference.hxx"))
189 return true;
190
191 // look for calls to the Reference<T>.set(x, UNO_QUERY) constructor
192 auto method = mce->getMethodDecl();
193 if (!method || !method->getIdentifier() || method->getName() != "set")
194 return true;
195 if (mce->getNumArgs() != 2)
196 return true;
197
198 auto methodRecordDecl = dyn_cast<ClassTemplateSpecializationDecl>(mce->getRecordDecl());
199 if (!methodRecordDecl || !methodRecordDecl->getIdentifier()
200 || methodRecordDecl->getName() != "Reference")
201 return true;
202
203 if (CheckForUnnecessaryGet(mce->getArg(0)))
204 report(DiagnosticsEngine::Warning, "unnecessary get() call", compat::getBeginLoc(mce))
205 << mce->getSourceRange();
206
207 // extract the type parameter passed to the template
208 const RecordType* templateParamType
209 = dyn_cast<RecordType>(methodRecordDecl->getTemplateArgs()[0].getAsType());
210 if (!templateParamType)
211 return true;
212
213 // extract the type of the first parameter passed to the method
214 const Expr* arg0 = mce->getArg(0);
215 if (!arg0)
216 return true;
217
218 // drill down the expression tree till we hit the bottom, because at the top, the type is BaseReference
219 const clang::Type* argType;
220 for (;;)
221 {
222 if (auto castExpr = dyn_cast<CastExpr>(arg0))
223 {
224 arg0 = castExpr->getSubExpr();
225 continue;
226 }
227 if (auto matTempExpr = dyn_cast<MaterializeTemporaryExpr>(arg0))
228 {
229 arg0 = matTempExpr->GetTemporaryExpr();
230 continue;
231 }
232 if (auto bindTempExpr = dyn_cast<CXXBindTemporaryExpr>(arg0))
233 {
234 arg0 = bindTempExpr->getSubExpr();
235 continue;
236 }
237 if (auto tempObjExpr = dyn_cast<CXXTemporaryObjectExpr>(arg0))
238 {
239 arg0 = tempObjExpr->getArg(0);
240 continue;
241 }
242 if (auto parenExpr = dyn_cast<ParenExpr>(arg0))
243 {
244 arg0 = parenExpr->getSubExpr();
245 continue;
246 }
247 argType = arg0->getType().getTypePtr();
248 break;
249 }
250
251 const RecordType* argTemplateType = extractTemplateType(argType);
252 if (!argTemplateType)
253 return true;
254
255 CXXRecordDecl* templateParamRD = dyn_cast<CXXRecordDecl>(templateParamType->getDecl());
256 CXXRecordDecl* methodArgRD = dyn_cast<CXXRecordDecl>(argTemplateType->getDecl());
257
258 // querying for XInterface (instead of doing an upcast) has special semantics,
259 // to check for UNO object equivalence.
260 if (templateParamRD->getName() == "XInterface")
261 return true;
262
263 // XShape is used in UNO aggregates in very "entertaining" ways, which means an UNO_QUERY
264 // can return a completely different object, e.g. see SwXShape::queryInterface
265 if (templateParamRD->getName() == "XShape")
266 return true;
267
268 if (auto declRefExpr = dyn_cast<DeclRefExpr>(mce->getArg(1)))
269 {
270 // no warning expected, used to reject null references
271 if (auto enumConstantDecl = dyn_cast<EnumConstantDecl>(declRefExpr->getDecl()))
272 {
273 if (enumConstantDecl->getName() == "UNO_SET_THROW")
274 return true;
275 if (enumConstantDecl->getName() == "UNO_QUERY_THROW")
276 return true;
277 if (enumConstantDecl->getName() == "SAL_NO_ACQUIRE")
278 return true;
279 }
280 }
281
282 if (methodArgRD->Equals(templateParamRD) || isDerivedFrom(methodArgRD, templateParamRD))
283 {
284 report(DiagnosticsEngine::Warning,
285 "the source reference is already a subtype of the destination reference, just use =",
286 compat::getBeginLoc(mce))
287 << mce->getSourceRange();
288 }
289 return true;
290 }
291
292 /**
293 Check for
294 Reference<T>(x.get(), UNO_QUERY)
295 because sometimes simplifying that means the main purpose of this plugin can kick in.
296 */
CheckForUnnecessaryGet(const Expr * expr)297 bool ReferenceCasting::CheckForUnnecessaryGet(const Expr* expr)
298 {
299 expr = expr->IgnoreImplicit();
300 auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(expr);
301 if (!cxxMemberCallExpr)
302 return false;
303 auto methodDecl = cxxMemberCallExpr->getMethodDecl();
304 if (!methodDecl)
305 return false;
306 if (!methodDecl->getIdentifier() || methodDecl->getName() != "get")
307 return false;
308
309 if (!loplugin::TypeCheck(expr->getType()).Pointer())
310 return false;
311 if (!loplugin::DeclCheck(methodDecl->getParent()).Class("Reference").Namespace("uno"))
312 return false;
313
314 StringRef aFileName = getFilenameOfLocation(
315 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr)));
316 if (loplugin::isSamePathname(aFileName, SRCDIR "/cppu/qa/test_reference.cxx"))
317 return false;
318
319 return true;
320 }
321
extractTemplateType(const clang::Type * cceType)322 static const RecordType* extractTemplateType(const clang::Type* cceType)
323 {
324 // check for passing raw pointer to interface case
325 if (cceType->isPointerType())
326 {
327 auto pointeeType = cceType->getPointeeType();
328 if (auto elaboratedType = dyn_cast<ElaboratedType>(pointeeType))
329 pointeeType = elaboratedType->desugar();
330 if (auto recordType = dyn_cast<RecordType>(pointeeType))
331 return recordType;
332 }
333
334 if (auto elaboratedType = dyn_cast<ElaboratedType>(cceType))
335 cceType = elaboratedType->desugar().getTypePtr();
336 auto cceTST = dyn_cast<TemplateSpecializationType>(cceType);
337 if (!cceTST)
338 return NULL;
339 if (cceTST->getNumArgs() != 1)
340 return NULL;
341 const TemplateArgument& cceTA = cceTST->getArg(0);
342 const clang::Type* templateParamType = cceTA.getAsType().getTypePtr();
343 if (auto elaboratedType = dyn_cast<ElaboratedType>(templateParamType))
344 templateParamType = elaboratedType->desugar().getTypePtr();
345 return dyn_cast<RecordType>(templateParamType);
346 }
347
348 /**
349 Implement my own isDerived because we can't always see all the definitions of the classes involved.
350 which will cause an assert with the normal clang isDerivedFrom code.
351 */
isDerivedFrom(const CXXRecordDecl * subtypeRecord,const CXXRecordDecl * baseRecord)352 static bool isDerivedFrom(const CXXRecordDecl* subtypeRecord, const CXXRecordDecl* baseRecord)
353 {
354 // if there is more than one case, then we have an ambiguous conversion, and we can't change the code
355 // to use the upcasting constructor.
356 return loplugin::derivedFromCount(subtypeRecord, baseRecord) == 1;
357 }
358
359 loplugin::Plugin::Registration<ReferenceCasting> referencecasting("referencecasting");
360
361 } // namespace
362
363 #endif // LO_CLANG_SHARED_PLUGINS
364
365 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
366