Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / xmlimport.cxx
blob1f9f44090b8a87b11a556d5f6a17ade0e40faaa9
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>
17 #include <unordered_map>
18 #include "clang/AST/CXXInheritance.h"
21 * This is a compile-time checker.
23 * Check that when we override SvXmlImportContext, and we override createFastChildContext,
24 * we have also overridden startFastElement, or the fast-parser stuff will not work
25 * correctly.
28 namespace
30 class XmlImport : public loplugin::FilteringPlugin<XmlImport>
32 public:
33 explicit XmlImport(loplugin::InstantiationData const& data)
34 : FilteringPlugin(data)
38 bool preRun() override
40 StringRef fn(handler.getMainFileName());
41 if (loplugin::isSamePathname(fn, SRCDIR "/xmloff/source/core/xmlictxt.cxx"))
42 return false;
43 if (loplugin::isSamePathname(fn, SRCDIR "/xmloff/source/core/xmlimp.cxx"))
44 return false;
45 // These are mostly classes delegating calls to other classes
46 if (loplugin::isSamePathname(fn, SRCDIR "/xmloff/source/text/XMLTextFrameContext.cxx"))
47 return false;
48 if (loplugin::isSamePathname(fn, SRCDIR "/xmloff/source/draw/ximpshap.cxx"))
49 return false;
50 if (loplugin::isSamePathname(fn, SRCDIR "/xmloff/source/table/XMLTableImport.cxx"))
51 return false;
52 if (loplugin::isSamePathname(fn,
53 SRCDIR "/sc/source/filter/xml/XMLTrackedChangesContext.cxx"))
54 return false;
55 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/filter/xml/xmlannoi.cxx"))
56 return false;
57 // this class specifically wants to prevent some endFastElement processing happening in its superclass
58 if (loplugin::isSamePathname(fn, SRCDIR
59 "/xmloff/source/text/XMLIndexBibliographySourceContext.cxx"))
60 return false;
61 // calling mxSlaveContext
62 if (loplugin::isSamePathname(fn, SRCDIR "/xmloff/source/draw/XMLNumberStyles.cxx"))
63 return false;
64 return true;
67 void run() override
69 if (preRun())
71 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
75 bool VisitCXXMethodDecl(const CXXMethodDecl*);
76 bool VisitCXXMemberCallExpr(const CXXMemberCallExpr*);
77 bool VisitBinaryOperator(const BinaryOperator*);
78 bool VisitSwitchStmt(const SwitchStmt*);
79 bool VisitCallExpr(const CallExpr*);
81 private:
82 bool isXmlTokEnum(const Expr*);
83 bool isUInt16(const Expr*);
84 bool isUInt16(QualType);
86 std::unordered_map<const CXXRecordDecl*, const CXXMethodDecl*> startFastElementSet;
87 std::unordered_map<const CXXRecordDecl*, const CXXMethodDecl*> StartElementSet;
88 std::unordered_map<const CXXRecordDecl*, const CXXMethodDecl*> endFastElementSet;
89 std::unordered_map<const CXXRecordDecl*, const CXXMethodDecl*> EndElementSet;
90 std::unordered_map<const CXXRecordDecl*, const CXXMethodDecl*> charactersSet;
91 std::unordered_map<const CXXRecordDecl*, const CXXMethodDecl*> CharactersSet;
94 bool XmlImport::VisitCXXMethodDecl(const CXXMethodDecl* methodDecl)
96 auto beginLoc = methodDecl->getBeginLoc();
97 if (!beginLoc.isValid() || ignoreLocation(beginLoc))
98 return true;
100 if (!methodDecl->getIdentifier())
101 return true;
103 auto cxxRecordDecl = methodDecl->getParent();
104 if (!cxxRecordDecl || !cxxRecordDecl->getIdentifier())
105 return true;
107 if (loplugin::DeclCheck(cxxRecordDecl).Class("SvXMLImportContext"))
108 return true;
110 if (!loplugin::isDerivedFrom(cxxRecordDecl, [](Decl const* decl) -> bool {
111 auto const dc = loplugin::DeclCheck(decl);
112 return bool(dc.ClassOrStruct("SvXMLImportContext").GlobalNamespace());
114 return true;
116 auto name = methodDecl->getName();
117 if (name == "startFastElement")
118 startFastElementSet.insert({ cxxRecordDecl, methodDecl });
119 else if (name == "StartElement")
120 StartElementSet.insert({ cxxRecordDecl, methodDecl });
121 else if (name == "endFastElement")
122 endFastElementSet.insert({ cxxRecordDecl, methodDecl });
123 else if (name == "EndElement")
124 EndElementSet.insert({ cxxRecordDecl, methodDecl });
125 else if (name == "characters")
127 if (methodDecl->getNumParams() == 1)
128 charactersSet.insert({ cxxRecordDecl, methodDecl });
130 else if (name == "Characters")
132 if (methodDecl->getNumParams() == 1)
133 CharactersSet.insert({ cxxRecordDecl, methodDecl });
137 auto it1 = endFastElementSet.find(cxxRecordDecl);
138 auto it2 = EndElementSet.find(cxxRecordDecl);
139 if (it1 != endFastElementSet.end() && it2 != EndElementSet.end())
141 auto methodDecl1 = it1->second;
142 report(DiagnosticsEngine::Warning, "cannot override both endFastElement and EndElement",
143 methodDecl1->getBeginLoc())
144 << methodDecl1->getSourceRange();
145 auto methodDecl2 = it2->second;
146 report(DiagnosticsEngine::Warning, "cannot override both endFastElement and EndElement",
147 methodDecl2->getBeginLoc())
148 << methodDecl2->getSourceRange();
153 auto it1 = startFastElementSet.find(cxxRecordDecl);
154 auto it2 = StartElementSet.find(cxxRecordDecl);
155 if (it1 != startFastElementSet.end() && it2 != StartElementSet.end())
157 auto methodDecl1 = it1->second;
158 report(DiagnosticsEngine::Warning,
159 "cannot override both startFastElement and StartElement",
160 methodDecl1->getBeginLoc())
161 << methodDecl1->getSourceRange();
162 auto methodDecl2 = it2->second;
163 report(DiagnosticsEngine::Warning,
164 "cannot override both startFastElement and StartElement",
165 methodDecl2->getBeginLoc())
166 << methodDecl2->getSourceRange();
170 auto it1 = charactersSet.find(cxxRecordDecl);
171 auto it2 = CharactersSet.find(cxxRecordDecl);
172 if (it1 != charactersSet.end() && it2 != CharactersSet.end())
174 auto methodDecl1 = it1->second;
175 report(DiagnosticsEngine::Warning, "cannot override both characters and Characters",
176 methodDecl1->getBeginLoc())
177 << methodDecl1->getSourceRange();
178 auto methodDecl2 = it2->second;
179 report(DiagnosticsEngine::Warning, "cannot override both characters and Characters",
180 methodDecl2->getBeginLoc())
181 << methodDecl2->getSourceRange();
185 auto checkEmpty = [&]() {
186 if (!methodDecl->isThisDeclarationADefinition())
187 return;
188 auto compoundStmt = dyn_cast_or_null<CompoundStmt>(methodDecl->getBody());
189 if (compoundStmt == nullptr || compoundStmt->size() > 0)
190 return;
191 report(DiagnosticsEngine::Warning, "empty, should be removed", methodDecl->getBeginLoc())
192 << methodDecl->getSourceRange();
193 auto canonicalDecl = methodDecl->getCanonicalDecl();
194 if (canonicalDecl != methodDecl)
195 report(DiagnosticsEngine::Note, "definition here", canonicalDecl->getBeginLoc())
196 << canonicalDecl->getSourceRange();
198 auto checkOnlyReturn = [&]() {
199 if (!methodDecl->isThisDeclarationADefinition())
200 return;
201 auto compoundStmt = dyn_cast_or_null<CompoundStmt>(methodDecl->getBody());
202 if (compoundStmt == nullptr || compoundStmt->size() > 1)
203 return;
204 auto returnStmt = dyn_cast_or_null<ReturnStmt>(*compoundStmt->body_begin());
205 if (!returnStmt)
206 return;
207 auto cxxConstructExpr
208 = dyn_cast_or_null<CXXConstructExpr>(returnStmt->getRetValue()->IgnoreImplicit());
209 if (!cxxConstructExpr)
210 return;
211 if (cxxConstructExpr->getNumArgs() != 1)
212 return;
213 if (!isa<CXXNullPtrLiteralExpr>(cxxConstructExpr->getArg(0)->IgnoreImplicit()))
214 return;
215 report(DiagnosticsEngine::Warning, "empty, should be removed", methodDecl->getBeginLoc())
216 << methodDecl->getSourceRange();
217 auto canonicalDecl = methodDecl->getCanonicalDecl();
218 if (canonicalDecl != methodDecl)
219 report(DiagnosticsEngine::Note, "definition here", canonicalDecl->getBeginLoc())
220 << canonicalDecl->getSourceRange();
223 if (name == "startFastElement")
224 checkEmpty();
225 else if (name == "endFastElement")
226 checkEmpty();
227 else if (name == "characters")
228 checkEmpty();
229 else if (name == "createFastChildContext")
230 checkOnlyReturn();
231 else if (name == "createUnknownChildContext")
232 checkOnlyReturn();
234 return true;
237 bool XmlImport::VisitCXXMemberCallExpr(const CXXMemberCallExpr* callExpr)
239 auto beginLoc = callExpr->getBeginLoc();
240 if (!beginLoc.isValid() || ignoreLocation(callExpr))
241 return true;
243 CXXMethodDecl* methodDecl = callExpr->getMethodDecl();
244 if (!methodDecl || !methodDecl->getIdentifier())
245 return true;
247 auto cxxRecordDecl = methodDecl->getParent();
248 if (!cxxRecordDecl || !cxxRecordDecl->getIdentifier())
249 return true;
251 if (!loplugin::DeclCheck(cxxRecordDecl).Class("SvXMLImportContext"))
252 return true;
254 auto name = methodDecl->getName();
255 if (name == "startFastElement" || name == "characters" || name == "endFastElement"
256 || name == "createFastChildContext" || name == "createUnknownChildContext"
257 || name == "StartElement" || name == "EndElement" || name == "Characters"
258 || name == "CreateChildContext")
261 * Calling this superclass method from a subclass method will mess with the fallback logic in the superclass.
263 report(DiagnosticsEngine::Warning, "don't call this superclass method",
264 callExpr->getBeginLoc())
265 << callExpr->getSourceRange();
267 return true;
270 bool XmlImport::VisitBinaryOperator(const BinaryOperator* binaryOp)
272 auto beginLoc = binaryOp->getBeginLoc();
273 if (!beginLoc.isValid() || ignoreLocation(binaryOp))
274 return true;
275 auto op = binaryOp->getOpcode();
276 if (op != BO_EQ && op != BO_NE)
277 return true;
278 auto check2 = [&](const Expr* expr) -> void {
279 if (!isUInt16(expr))
280 report(DiagnosticsEngine::Warning,
281 "comparing XML_TOK enum to 'sal_uInt32', expected sal_uInt16",
282 binaryOp->getBeginLoc())
283 << binaryOp->getSourceRange();
285 if (isXmlTokEnum(binaryOp->getLHS()))
286 check2(binaryOp->getRHS());
287 else if (isXmlTokEnum(binaryOp->getRHS()))
288 check2(binaryOp->getLHS());
289 return true;
292 bool XmlImport::VisitSwitchStmt(const SwitchStmt* switchStmt)
294 auto beginLoc = switchStmt->getBeginLoc();
295 if (!beginLoc.isValid() || ignoreLocation(switchStmt))
296 return true;
297 if (isUInt16(switchStmt->getCond()))
298 return true;
299 // if the condition is an enum type, ignore this switch
300 auto condEnumType = switchStmt->getCond()
301 ->IgnoreImplicit()
302 ->getType()
303 ->getUnqualifiedDesugaredType()
304 ->getAs<EnumType>();
305 if (condEnumType)
306 return true;
307 auto switchCaseStmt = switchStmt->getSwitchCaseList();
308 for (; switchCaseStmt != nullptr; switchCaseStmt = switchCaseStmt->getNextSwitchCase())
310 auto caseStmt = dyn_cast<CaseStmt>(switchCaseStmt);
311 if (!caseStmt)
312 continue;
313 if (!isXmlTokEnum(caseStmt->getLHS()))
314 continue;
315 report(DiagnosticsEngine::Warning,
316 "comparing XML_TOK enum to 'sal_uInt32', expected sal_uInt16",
317 caseStmt->getBeginLoc())
318 << caseStmt->getSourceRange();
320 return true;
323 bool XmlImport::VisitCallExpr(const CallExpr* callExpr)
325 auto beginLoc = callExpr->getBeginLoc();
326 if (!beginLoc.isValid() || ignoreLocation(callExpr))
327 return true;
329 const FunctionDecl* functionDecl;
330 if (isa<CXXMemberCallExpr>(callExpr))
331 functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
332 else
333 functionDecl = callExpr->getDirectCallee();
334 if (!functionDecl)
335 return true;
336 for (unsigned i = 0; i != callExpr->getNumArgs(); ++i)
338 auto argExpr = callExpr->getArg(i)->IgnoreImplicit();
339 if (!isXmlTokEnum(argExpr))
340 continue;
341 // if the condition is an enum type, ignore this switch
342 auto condEnumType = functionDecl->getParamDecl(i)
343 ->getType()
344 ->getUnqualifiedDesugaredType()
345 ->getAs<EnumType>();
346 if (condEnumType)
347 continue;
348 if (isUInt16(functionDecl->getParamDecl(i)->getType()))
349 return true;
350 report(DiagnosticsEngine::Warning,
351 "passing XML_TOK enum to 'sal_Int32', wrong param or XML token type",
352 callExpr->getBeginLoc())
353 << callExpr->getSourceRange();
356 return true;
359 bool XmlImport::isXmlTokEnum(const Expr* expr)
361 expr = expr->IgnoreImplicit();
362 // check that we have an unscoped enum type
363 auto condEnumType = expr->getType()->getUnqualifiedDesugaredType()->getAs<EnumType>();
364 if (!condEnumType || condEnumType->getDecl()->isScoped())
365 return false;
366 auto declRefExpr = dyn_cast<DeclRefExpr>(expr);
367 if (!declRefExpr)
368 return false;
369 auto enumConstant = dyn_cast<EnumConstantDecl>(declRefExpr->getDecl());
370 if (!enumConstant)
371 return false;
372 return enumConstant->getIdentifier() && enumConstant->getName().startswith("XML_TOK_");
375 bool XmlImport::isUInt16(const Expr* expr)
377 expr = expr->IgnoreImplicit();
378 return isUInt16(expr->getType());
381 bool XmlImport::isUInt16(QualType qt)
383 if (qt->isSpecificBuiltinType(BuiltinType::UShort))
384 return true;
385 return bool(loplugin::TypeCheck(qt).Typedef("sal_uInt16"));
388 loplugin::Plugin::Registration<XmlImport> xmlimport("xmlimport");
390 } // namespace
392 #endif // LO_CLANG_SHARED_PLUGINS
394 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */