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
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
= compat::getBeginLoc(methodDecl
);
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 compat::getBeginLoc(methodDecl1
))
144 << methodDecl1
->getSourceRange();
145 auto methodDecl2
= it2
->second
;
146 report(DiagnosticsEngine::Warning
, "cannot override both endFastElement and EndElement",
147 compat::getBeginLoc(methodDecl2
))
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 compat::getBeginLoc(methodDecl1
))
161 << methodDecl1
->getSourceRange();
162 auto methodDecl2
= it2
->second
;
163 report(DiagnosticsEngine::Warning
,
164 "cannot override both startFastElement and StartElement",
165 compat::getBeginLoc(methodDecl2
))
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 compat::getBeginLoc(methodDecl1
))
177 << methodDecl1
->getSourceRange();
178 auto methodDecl2
= it2
->second
;
179 report(DiagnosticsEngine::Warning
, "cannot override both characters and Characters",
180 compat::getBeginLoc(methodDecl2
))
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",
192 compat::getBeginLoc(methodDecl
))
193 << methodDecl
->getSourceRange();
194 auto canonicalDecl
= methodDecl
->getCanonicalDecl();
195 if (canonicalDecl
!= methodDecl
)
196 report(DiagnosticsEngine::Note
, "definition here", compat::getBeginLoc(canonicalDecl
))
197 << canonicalDecl
->getSourceRange();
199 auto checkOnlyReturn
= [&]() {
200 if (!methodDecl
->isThisDeclarationADefinition())
202 auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(methodDecl
->getBody());
203 if (compoundStmt
== nullptr || compoundStmt
->size() > 1)
205 auto returnStmt
= dyn_cast_or_null
<ReturnStmt
>(*compoundStmt
->body_begin());
208 auto cxxConstructExpr
209 = dyn_cast_or_null
<CXXConstructExpr
>(returnStmt
->getRetValue()->IgnoreImplicit());
210 if (!cxxConstructExpr
)
212 if (cxxConstructExpr
->getNumArgs() != 1)
214 if (!isa
<CXXNullPtrLiteralExpr
>(cxxConstructExpr
->getArg(0)->IgnoreImplicit()))
216 report(DiagnosticsEngine::Warning
, "empty, should be removed",
217 compat::getBeginLoc(methodDecl
))
218 << methodDecl
->getSourceRange();
219 auto canonicalDecl
= methodDecl
->getCanonicalDecl();
220 if (canonicalDecl
!= methodDecl
)
221 report(DiagnosticsEngine::Note
, "definition here", compat::getBeginLoc(canonicalDecl
))
222 << canonicalDecl
->getSourceRange();
225 if (name
== "startFastElement")
227 else if (name
== "endFastElement")
229 else if (name
== "characters")
231 else if (name
== "createFastChildContext")
233 else if (name
== "createUnknownChildContext")
239 bool XmlImport::VisitCXXMemberCallExpr(const CXXMemberCallExpr
* callExpr
)
241 auto beginLoc
= compat::getBeginLoc(callExpr
);
242 if (!beginLoc
.isValid() || ignoreLocation(callExpr
))
245 CXXMethodDecl
* methodDecl
= callExpr
->getMethodDecl();
246 if (!methodDecl
|| !methodDecl
->getIdentifier())
249 auto cxxRecordDecl
= methodDecl
->getParent();
250 if (!cxxRecordDecl
|| !cxxRecordDecl
->getIdentifier())
253 if (!loplugin::DeclCheck(cxxRecordDecl
).Class("SvXMLImportContext"))
256 auto name
= methodDecl
->getName();
257 if (name
== "startFastElement" || name
== "characters" || name
== "endFastElement"
258 || name
== "createFastChildContext" || name
== "createUnknownChildContext"
259 || name
== "StartElement" || name
== "EndElement" || name
== "Characters"
260 || name
== "CreateChildContext")
263 * Calling this superclass method from a subclass method will mess with the fallback logic in the superclass.
265 report(DiagnosticsEngine::Warning
, "don't call this superclass method",
266 compat::getBeginLoc(callExpr
))
267 << callExpr
->getSourceRange();
272 bool XmlImport::VisitBinaryOperator(const BinaryOperator
* binaryOp
)
274 auto beginLoc
= compat::getBeginLoc(binaryOp
);
275 if (!beginLoc
.isValid() || ignoreLocation(binaryOp
))
277 auto op
= binaryOp
->getOpcode();
278 if (op
!= BO_EQ
&& op
!= BO_NE
)
280 auto check2
= [&](const Expr
* expr
) -> void {
282 report(DiagnosticsEngine::Warning
,
283 "comparing XML_TOK enum to 'sal_uInt32', expected sal_uInt16",
284 compat::getBeginLoc(binaryOp
))
285 << binaryOp
->getSourceRange();
287 if (isXmlTokEnum(binaryOp
->getLHS()))
288 check2(binaryOp
->getRHS());
289 else if (isXmlTokEnum(binaryOp
->getRHS()))
290 check2(binaryOp
->getLHS());
294 bool XmlImport::VisitSwitchStmt(const SwitchStmt
* switchStmt
)
296 auto beginLoc
= compat::getBeginLoc(switchStmt
);
297 if (!beginLoc
.isValid() || ignoreLocation(switchStmt
))
299 if (isUInt16(switchStmt
->getCond()))
301 // if the condition is an enum type, ignore this switch
302 auto condEnumType
= compat::IgnoreImplicit(switchStmt
->getCond())
304 ->getUnqualifiedDesugaredType()
308 auto switchCaseStmt
= switchStmt
->getSwitchCaseList();
309 for (; switchCaseStmt
!= nullptr; switchCaseStmt
= switchCaseStmt
->getNextSwitchCase())
311 auto caseStmt
= dyn_cast
<CaseStmt
>(switchCaseStmt
);
314 if (!isXmlTokEnum(caseStmt
->getLHS()))
316 report(DiagnosticsEngine::Warning
,
317 "comparing XML_TOK enum to 'sal_uInt32', expected sal_uInt16",
318 compat::getBeginLoc(caseStmt
))
319 << caseStmt
->getSourceRange();
324 bool XmlImport::VisitCallExpr(const CallExpr
* callExpr
)
326 auto beginLoc
= compat::getBeginLoc(callExpr
);
327 if (!beginLoc
.isValid() || ignoreLocation(callExpr
))
330 const FunctionDecl
* functionDecl
;
331 if (isa
<CXXMemberCallExpr
>(callExpr
))
332 functionDecl
= dyn_cast
<CXXMemberCallExpr
>(callExpr
)->getMethodDecl();
334 functionDecl
= callExpr
->getDirectCallee();
337 for (unsigned i
= 0; i
!= callExpr
->getNumArgs(); ++i
)
339 auto argExpr
= compat::IgnoreImplicit(callExpr
->getArg(i
));
340 if (!isXmlTokEnum(argExpr
))
342 // if the condition is an enum type, ignore this switch
343 auto condEnumType
= functionDecl
->getParamDecl(i
)
345 ->getUnqualifiedDesugaredType()
349 if (isUInt16(functionDecl
->getParamDecl(i
)->getType()))
351 report(DiagnosticsEngine::Warning
,
352 "passing XML_TOK enum to 'sal_Int32', wrong param or XML token type",
353 compat::getBeginLoc(callExpr
))
354 << callExpr
->getSourceRange();
360 bool XmlImport::isXmlTokEnum(const Expr
* expr
)
362 expr
= compat::IgnoreImplicit(expr
);
363 // check that we have an unscoped enum type
364 auto condEnumType
= expr
->getType()->getUnqualifiedDesugaredType()->getAs
<EnumType
>();
365 if (!condEnumType
|| condEnumType
->getDecl()->isScoped())
367 auto declRefExpr
= dyn_cast
<DeclRefExpr
>(expr
);
370 auto enumConstant
= dyn_cast
<EnumConstantDecl
>(declRefExpr
->getDecl());
373 return enumConstant
->getIdentifier() && enumConstant
->getName().startswith("XML_TOK_");
376 bool XmlImport::isUInt16(const Expr
* expr
)
378 expr
= compat::IgnoreImplicit(expr
);
379 return isUInt16(expr
->getType());
382 bool XmlImport::isUInt16(QualType qt
)
384 if (qt
->isSpecificBuiltinType(BuiltinType::UShort
))
386 return bool(loplugin::TypeCheck(qt
).Typedef("sal_uInt16"));
389 loplugin::Plugin::Registration
<XmlImport
> xmlimport("xmlimport");
393 #endif // LO_CLANG_SHARED_PLUGINS
395 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */