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"))
68 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
72 bool VisitCXXMethodDecl(const CXXMethodDecl
*);
73 bool VisitCXXMemberCallExpr(const CXXMemberCallExpr
*);
74 bool VisitBinaryOperator(const BinaryOperator
*);
75 bool VisitSwitchStmt(const SwitchStmt
*);
76 bool VisitCallExpr(const CallExpr
*);
79 bool isXmlTokEnum(const Expr
*);
80 bool isUInt16(const Expr
*);
81 bool isUInt16(QualType
);
83 std::unordered_map
<const CXXRecordDecl
*, const CXXMethodDecl
*> startFastElementSet
;
84 std::unordered_map
<const CXXRecordDecl
*, const CXXMethodDecl
*> StartElementSet
;
85 std::unordered_map
<const CXXRecordDecl
*, const CXXMethodDecl
*> endFastElementSet
;
86 std::unordered_map
<const CXXRecordDecl
*, const CXXMethodDecl
*> EndElementSet
;
87 std::unordered_map
<const CXXRecordDecl
*, const CXXMethodDecl
*> charactersSet
;
88 std::unordered_map
<const CXXRecordDecl
*, const CXXMethodDecl
*> CharactersSet
;
91 bool XmlImport::VisitCXXMethodDecl(const CXXMethodDecl
* methodDecl
)
93 auto beginLoc
= compat::getBeginLoc(methodDecl
);
94 if (!beginLoc
.isValid() || ignoreLocation(beginLoc
))
97 if (!methodDecl
->getIdentifier())
100 auto cxxRecordDecl
= methodDecl
->getParent();
101 if (!cxxRecordDecl
|| !cxxRecordDecl
->getIdentifier())
104 if (loplugin::DeclCheck(cxxRecordDecl
).Class("SvXMLImportContext"))
107 if (!loplugin::isDerivedFrom(cxxRecordDecl
, [](Decl
const* decl
) -> bool {
108 auto const dc
= loplugin::DeclCheck(decl
);
109 return bool(dc
.ClassOrStruct("SvXMLImportContext").GlobalNamespace());
113 auto name
= methodDecl
->getName();
114 if (name
== "startFastElement")
115 startFastElementSet
.insert({ cxxRecordDecl
, methodDecl
});
116 else if (name
== "StartElement")
117 StartElementSet
.insert({ cxxRecordDecl
, methodDecl
});
118 else if (name
== "endFastElement")
119 endFastElementSet
.insert({ cxxRecordDecl
, methodDecl
});
120 else if (name
== "EndElement")
121 EndElementSet
.insert({ cxxRecordDecl
, methodDecl
});
122 else if (name
== "characters")
124 if (methodDecl
->getNumParams() == 1)
125 charactersSet
.insert({ cxxRecordDecl
, methodDecl
});
127 else if (name
== "Characters")
129 if (methodDecl
->getNumParams() == 1)
130 CharactersSet
.insert({ cxxRecordDecl
, methodDecl
});
134 auto it1
= endFastElementSet
.find(cxxRecordDecl
);
135 auto it2
= EndElementSet
.find(cxxRecordDecl
);
136 if (it1
!= endFastElementSet
.end() && it2
!= EndElementSet
.end())
138 auto methodDecl1
= it1
->second
;
139 report(DiagnosticsEngine::Warning
, "cannot override both endFastElement and EndElement",
140 compat::getBeginLoc(methodDecl1
))
141 << methodDecl1
->getSourceRange();
142 auto methodDecl2
= it2
->second
;
143 report(DiagnosticsEngine::Warning
, "cannot override both endFastElement and EndElement",
144 compat::getBeginLoc(methodDecl2
))
145 << methodDecl2
->getSourceRange();
150 auto it1
= startFastElementSet
.find(cxxRecordDecl
);
151 auto it2
= StartElementSet
.find(cxxRecordDecl
);
152 if (it1
!= startFastElementSet
.end() && it2
!= StartElementSet
.end())
154 auto methodDecl1
= it1
->second
;
155 report(DiagnosticsEngine::Warning
,
156 "cannot override both startFastElement and StartElement",
157 compat::getBeginLoc(methodDecl1
))
158 << methodDecl1
->getSourceRange();
159 auto methodDecl2
= it2
->second
;
160 report(DiagnosticsEngine::Warning
,
161 "cannot override both startFastElement and StartElement",
162 compat::getBeginLoc(methodDecl2
))
163 << methodDecl2
->getSourceRange();
167 auto it1
= charactersSet
.find(cxxRecordDecl
);
168 auto it2
= CharactersSet
.find(cxxRecordDecl
);
169 if (it1
!= charactersSet
.end() && it2
!= CharactersSet
.end())
171 auto methodDecl1
= it1
->second
;
172 report(DiagnosticsEngine::Warning
, "cannot override both characters and Characters",
173 compat::getBeginLoc(methodDecl1
))
174 << methodDecl1
->getSourceRange();
175 auto methodDecl2
= it2
->second
;
176 report(DiagnosticsEngine::Warning
, "cannot override both characters and Characters",
177 compat::getBeginLoc(methodDecl2
))
178 << methodDecl2
->getSourceRange();
182 auto checkEmpty
= [&]() {
183 if (!methodDecl
->isThisDeclarationADefinition())
185 auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(methodDecl
->getBody());
186 if (compoundStmt
== nullptr || compoundStmt
->size() > 0)
188 report(DiagnosticsEngine::Warning
, "empty, should be removed",
189 compat::getBeginLoc(methodDecl
))
190 << methodDecl
->getSourceRange();
191 auto canonicalDecl
= methodDecl
->getCanonicalDecl();
192 if (canonicalDecl
!= methodDecl
)
193 report(DiagnosticsEngine::Note
, "definition here", compat::getBeginLoc(canonicalDecl
))
194 << canonicalDecl
->getSourceRange();
196 auto checkOnlyReturn
= [&]() {
197 if (!methodDecl
->isThisDeclarationADefinition())
199 auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(methodDecl
->getBody());
200 if (compoundStmt
== nullptr || compoundStmt
->size() > 1)
202 auto returnStmt
= dyn_cast_or_null
<ReturnStmt
>(*compoundStmt
->body_begin());
205 auto cxxConstructExpr
206 = dyn_cast_or_null
<CXXConstructExpr
>(returnStmt
->getRetValue()->IgnoreImplicit());
207 if (!cxxConstructExpr
)
209 if (cxxConstructExpr
->getNumArgs() != 1)
211 if (!isa
<CXXNullPtrLiteralExpr
>(cxxConstructExpr
->getArg(0)->IgnoreImplicit()))
213 report(DiagnosticsEngine::Warning
, "empty, should be removed",
214 compat::getBeginLoc(methodDecl
))
215 << methodDecl
->getSourceRange();
216 auto canonicalDecl
= methodDecl
->getCanonicalDecl();
217 if (canonicalDecl
!= methodDecl
)
218 report(DiagnosticsEngine::Note
, "definition here", compat::getBeginLoc(canonicalDecl
))
219 << canonicalDecl
->getSourceRange();
222 if (name
== "startFastElement")
224 else if (name
== "endFastElement")
226 else if (name
== "characters")
228 else if (name
== "createFastChildContext")
230 else if (name
== "createUnknownChildContext")
236 bool XmlImport::VisitCXXMemberCallExpr(const CXXMemberCallExpr
* callExpr
)
238 auto beginLoc
= compat::getBeginLoc(callExpr
);
239 if (!beginLoc
.isValid() || ignoreLocation(callExpr
))
242 CXXMethodDecl
* methodDecl
= callExpr
->getMethodDecl();
243 if (!methodDecl
|| !methodDecl
->getIdentifier())
246 auto cxxRecordDecl
= methodDecl
->getParent();
247 if (!cxxRecordDecl
|| !cxxRecordDecl
->getIdentifier())
250 if (!loplugin::DeclCheck(cxxRecordDecl
).Class("SvXMLImportContext"))
253 auto name
= methodDecl
->getName();
254 if (name
== "startFastElement" || name
== "characters" || name
== "endFastElement"
255 || name
== "createFastChildContext" || name
== "createUnknownChildContext"
256 || name
== "StartElement" || name
== "EndElement" || name
== "Characters"
257 || name
== "CreateChildContext")
260 * Calling this superclass method from a subclass method will mess with the fallback logic in the superclass.
262 report(DiagnosticsEngine::Warning
, "don't call this superclass method",
263 compat::getBeginLoc(callExpr
))
264 << callExpr
->getSourceRange();
269 bool XmlImport::VisitBinaryOperator(const BinaryOperator
* binaryOp
)
271 auto beginLoc
= compat::getBeginLoc(binaryOp
);
272 if (!beginLoc
.isValid() || ignoreLocation(binaryOp
))
274 auto op
= binaryOp
->getOpcode();
275 if (op
!= BO_EQ
&& op
!= BO_NE
)
277 auto check2
= [&](const Expr
* expr
) -> void {
279 report(DiagnosticsEngine::Warning
,
280 "comparing XML_TOK enum to 'sal_uInt32', expected sal_uInt16",
281 compat::getBeginLoc(binaryOp
))
282 << binaryOp
->getSourceRange();
284 if (isXmlTokEnum(binaryOp
->getLHS()))
285 check2(binaryOp
->getRHS());
286 else if (isXmlTokEnum(binaryOp
->getRHS()))
287 check2(binaryOp
->getLHS());
291 bool XmlImport::VisitSwitchStmt(const SwitchStmt
* switchStmt
)
293 auto beginLoc
= compat::getBeginLoc(switchStmt
);
294 if (!beginLoc
.isValid() || ignoreLocation(switchStmt
))
296 if (isUInt16(switchStmt
->getCond()))
298 // if the condition is an enum type, ignore this switch
299 auto condEnumType
= compat::IgnoreImplicit(switchStmt
->getCond())
301 ->getUnqualifiedDesugaredType()
305 auto switchCaseStmt
= switchStmt
->getSwitchCaseList();
306 for (; switchCaseStmt
!= nullptr; switchCaseStmt
= switchCaseStmt
->getNextSwitchCase())
308 auto caseStmt
= dyn_cast
<CaseStmt
>(switchCaseStmt
);
311 if (!isXmlTokEnum(caseStmt
->getLHS()))
313 report(DiagnosticsEngine::Warning
,
314 "comparing XML_TOK enum to 'sal_uInt32', expected sal_uInt16",
315 compat::getBeginLoc(caseStmt
))
316 << caseStmt
->getSourceRange();
321 bool XmlImport::VisitCallExpr(const CallExpr
* callExpr
)
323 auto beginLoc
= compat::getBeginLoc(callExpr
);
324 if (!beginLoc
.isValid() || ignoreLocation(callExpr
))
327 const FunctionDecl
* functionDecl
;
328 if (isa
<CXXMemberCallExpr
>(callExpr
))
329 functionDecl
= dyn_cast
<CXXMemberCallExpr
>(callExpr
)->getMethodDecl();
331 functionDecl
= callExpr
->getDirectCallee();
334 for (unsigned i
= 0; i
!= callExpr
->getNumArgs(); ++i
)
336 auto argExpr
= compat::IgnoreImplicit(callExpr
->getArg(i
));
337 if (!isXmlTokEnum(argExpr
))
339 // if the condition is an enum type, ignore this switch
340 auto condEnumType
= functionDecl
->getParamDecl(i
)
342 ->getUnqualifiedDesugaredType()
346 if (isUInt16(functionDecl
->getParamDecl(i
)->getType()))
348 report(DiagnosticsEngine::Warning
,
349 "passing XML_TOK enum to 'sal_Int32', wrong param or XML token type",
350 compat::getBeginLoc(callExpr
))
351 << callExpr
->getSourceRange();
357 bool XmlImport::isXmlTokEnum(const Expr
* expr
)
359 expr
= compat::IgnoreImplicit(expr
);
360 // check that we have an unscoped enum type
361 auto condEnumType
= expr
->getType()->getUnqualifiedDesugaredType()->getAs
<EnumType
>();
362 if (!condEnumType
|| condEnumType
->getDecl()->isScoped())
364 auto declRefExpr
= dyn_cast
<DeclRefExpr
>(expr
);
367 auto enumConstant
= dyn_cast
<EnumConstantDecl
>(declRefExpr
->getDecl());
370 return enumConstant
->getIdentifier() && enumConstant
->getName().startswith("XML_TOK_");
373 bool XmlImport::isUInt16(const Expr
* expr
)
375 expr
= compat::IgnoreImplicit(expr
);
376 return isUInt16(expr
->getType());
379 bool XmlImport::isUInt16(QualType qt
)
381 if (qt
->isSpecificBuiltinType(BuiltinType::UShort
))
383 return bool(loplugin::TypeCheck(qt
).Typedef("sal_uInt16"));
386 loplugin::Plugin::Registration
<XmlImport
> xmlimport("xmlimport");
390 #endif // LO_CLANG_SHARED_PLUGINS
392 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */