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"
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
30 class XmlImport
: public loplugin::FilteringPlugin
<XmlImport
>
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"))
43 if (loplugin::isSamePathname(fn
, SRCDIR
"/xmloff/source/core/xmlimp.cxx"))
45 // These are mostly classes delegating calls to other classes
46 if (loplugin::isSamePathname(fn
, SRCDIR
"/xmloff/source/text/XMLTextFrameContext.cxx"))
48 if (loplugin::isSamePathname(fn
, SRCDIR
"/xmloff/source/draw/ximpshap.cxx"))
50 if (loplugin::isSamePathname(fn
, SRCDIR
"/xmloff/source/table/XMLTableImport.cxx"))
52 if (loplugin::isSamePathname(fn
,
53 SRCDIR
"/sc/source/filter/xml/XMLTrackedChangesContext.cxx"))
55 if (loplugin::isSamePathname(fn
, SRCDIR
"/sc/source/filter/xml/xmlannoi.cxx"))
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"))
61 // calling mxSlaveContext
62 if (loplugin::isSamePathname(fn
, SRCDIR
"/xmloff/source/draw/XMLNumberStyles.cxx"))
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
*);
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
))
100 if (!methodDecl
->getIdentifier())
103 auto cxxRecordDecl
= methodDecl
->getParent();
104 if (!cxxRecordDecl
|| !cxxRecordDecl
->getIdentifier())
107 if (loplugin::DeclCheck(cxxRecordDecl
).Class("SvXMLImportContext"))
110 if (!loplugin::isDerivedFrom(cxxRecordDecl
, [](Decl
const* decl
) -> bool {
111 auto const dc
= loplugin::DeclCheck(decl
);
112 return bool(dc
.ClassOrStruct("SvXMLImportContext").GlobalNamespace());
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())
188 auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(methodDecl
->getBody());
189 if (compoundStmt
== nullptr || compoundStmt
->size() > 0)
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())
201 auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(methodDecl
->getBody());
202 if (compoundStmt
== nullptr || compoundStmt
->size() > 1)
204 auto returnStmt
= dyn_cast_or_null
<ReturnStmt
>(*compoundStmt
->body_begin());
207 auto cxxConstructExpr
208 = dyn_cast_or_null
<CXXConstructExpr
>(returnStmt
->getRetValue()->IgnoreImplicit());
209 if (!cxxConstructExpr
)
211 if (cxxConstructExpr
->getNumArgs() != 1)
213 if (!isa
<CXXNullPtrLiteralExpr
>(cxxConstructExpr
->getArg(0)->IgnoreImplicit()))
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")
225 else if (name
== "endFastElement")
227 else if (name
== "characters")
229 else if (name
== "createFastChildContext")
231 else if (name
== "createUnknownChildContext")
237 bool XmlImport::VisitCXXMemberCallExpr(const CXXMemberCallExpr
* callExpr
)
239 auto beginLoc
= callExpr
->getBeginLoc();
240 if (!beginLoc
.isValid() || ignoreLocation(callExpr
))
243 CXXMethodDecl
* methodDecl
= callExpr
->getMethodDecl();
244 if (!methodDecl
|| !methodDecl
->getIdentifier())
247 auto cxxRecordDecl
= methodDecl
->getParent();
248 if (!cxxRecordDecl
|| !cxxRecordDecl
->getIdentifier())
251 if (!loplugin::DeclCheck(cxxRecordDecl
).Class("SvXMLImportContext"))
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();
270 bool XmlImport::VisitBinaryOperator(const BinaryOperator
* binaryOp
)
272 auto beginLoc
= binaryOp
->getBeginLoc();
273 if (!beginLoc
.isValid() || ignoreLocation(binaryOp
))
275 auto op
= binaryOp
->getOpcode();
276 if (op
!= BO_EQ
&& op
!= BO_NE
)
278 auto check2
= [&](const Expr
* expr
) -> void {
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());
292 bool XmlImport::VisitSwitchStmt(const SwitchStmt
* switchStmt
)
294 auto beginLoc
= switchStmt
->getBeginLoc();
295 if (!beginLoc
.isValid() || ignoreLocation(switchStmt
))
297 if (isUInt16(switchStmt
->getCond()))
299 // if the condition is an enum type, ignore this switch
300 auto condEnumType
= switchStmt
->getCond()
303 ->getUnqualifiedDesugaredType()
307 auto switchCaseStmt
= switchStmt
->getSwitchCaseList();
308 for (; switchCaseStmt
!= nullptr; switchCaseStmt
= switchCaseStmt
->getNextSwitchCase())
310 auto caseStmt
= dyn_cast
<CaseStmt
>(switchCaseStmt
);
313 if (!isXmlTokEnum(caseStmt
->getLHS()))
315 report(DiagnosticsEngine::Warning
,
316 "comparing XML_TOK enum to 'sal_uInt32', expected sal_uInt16",
317 caseStmt
->getBeginLoc())
318 << caseStmt
->getSourceRange();
323 bool XmlImport::VisitCallExpr(const CallExpr
* callExpr
)
325 auto beginLoc
= callExpr
->getBeginLoc();
326 if (!beginLoc
.isValid() || ignoreLocation(callExpr
))
329 const FunctionDecl
* functionDecl
;
330 if (isa
<CXXMemberCallExpr
>(callExpr
))
331 functionDecl
= dyn_cast
<CXXMemberCallExpr
>(callExpr
)->getMethodDecl();
333 functionDecl
= callExpr
->getDirectCallee();
336 for (unsigned i
= 0; i
!= callExpr
->getNumArgs(); ++i
)
338 auto argExpr
= callExpr
->getArg(i
)->IgnoreImplicit();
339 if (!isXmlTokEnum(argExpr
))
341 // if the condition is an enum type, ignore this switch
342 auto condEnumType
= functionDecl
->getParamDecl(i
)
344 ->getUnqualifiedDesugaredType()
348 if (isUInt16(functionDecl
->getParamDecl(i
)->getType()))
350 report(DiagnosticsEngine::Warning
,
351 "passing XML_TOK enum to 'sal_Int32', wrong param or XML token type",
352 callExpr
->getBeginLoc())
353 << callExpr
->getSourceRange();
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())
366 auto declRefExpr
= dyn_cast
<DeclRefExpr
>(expr
);
369 auto enumConstant
= dyn_cast
<EnumConstantDecl
>(declRefExpr
->getDecl());
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
))
385 return bool(loplugin::TypeCheck(qt
).Typedef("sal_uInt16"));
388 loplugin::Plugin::Registration
<XmlImport
> xmlimport("xmlimport");
392 #endif // LO_CLANG_SHARED_PLUGINS
394 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */