nss: upgrade to release 3.73
[LibreOffice.git] / compilerplugins / clang / toolslong.cxx
blob26105a2697d8702de9ba1695d91582aa56870f24
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 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <algorithm>
11 #include <cassert>
12 #include <limits>
13 #include <map>
14 #include <string>
15 #include <iostream>
17 #include "clang/AST/Attr.h"
18 #include "clang/Basic/Builtins.h"
20 #include "config_clang.h"
22 #include "check.hxx"
23 #include "compat.hxx"
24 #include "plugin.hxx"
26 namespace
28 bool isLong(QualType type)
30 type = type.getNonReferenceType();
31 // ignore sal_Int64
32 if (type->getAs<TypedefType>())
33 return false;
34 // some parts of the STL have ::difference_type => long
35 if (type->getAs<AutoType>() || type->getAs<DecltypeType>())
36 return false;
37 #if CLANG_VERSION < 80000
38 // Prior to <https://github.com/llvm/llvm-project/commit/
39 // c50240dac133451b3eae5b89cecca4c1c4af9fd4> "[AST] Get aliased type info from an aliased
40 // TemplateSpecialization" in Clang 8, if type is a TemplateSpecializationType on top of a
41 // TypedefType, the above getAs<TypedefType> returned null (as it unconditionally desugared the
42 // TemplateSpecializationType to the underlying canonic type, not to any aliased type), so re-
43 // check with the TemplateSpecializationType's aliased type:
44 if (auto const t = type->getAs<TemplateSpecializationType>())
46 if (t->isTypeAlias())
48 return isLong(t->getAliasedType());
51 #endif
52 if (type->isSpecificBuiltinType(BuiltinType::Kind::Long))
53 return true;
54 auto arrayType = type->getAsArrayTypeUnsafe();
55 if (arrayType)
56 return isLong(arrayType->getElementType());
57 if (type->isPointerType())
58 return isLong(type->getPointeeType());
59 return false;
62 enum class OverrideKind
64 NO,
65 YES,
66 MAYBE
69 OverrideKind getOverrideKind(FunctionDecl const* decl)
71 CXXMethodDecl const* m = dyn_cast<CXXMethodDecl>(decl);
72 if (m == nullptr)
73 return OverrideKind::NO;
74 if (m->size_overridden_methods() != 0 || m->hasAttr<OverrideAttr>())
75 return OverrideKind::YES;
76 if (!dyn_cast<CXXRecordDecl>(m->getDeclContext())->hasAnyDependentBases())
77 return OverrideKind::NO;
78 return OverrideKind::MAYBE;
81 class ToolsLong : public loplugin::FilteringRewritePlugin<ToolsLong>
83 public:
84 explicit ToolsLong(loplugin::InstantiationData const& data)
85 : loplugin::FilteringRewritePlugin<ToolsLong>(data)
89 virtual void run() override;
91 bool VisitCStyleCastExpr(CStyleCastExpr* expr);
93 bool VisitCXXStaticCastExpr(CXXStaticCastExpr* expr);
95 bool VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr);
97 bool WalkUpFromParmVarDecl(ParmVarDecl const* decl);
98 bool VisitParmVarDecl(ParmVarDecl const* decl);
100 bool WalkUpFromVarDecl(VarDecl const* decl);
101 bool VisitVarDecl(VarDecl const* decl);
103 bool WalkUpFromFieldDecl(FieldDecl const* decl);
104 bool VisitFieldDecl(FieldDecl const* decl);
106 bool WalkUpFromFunctionDecl(FunctionDecl const* decl);
107 bool VisitFunctionDecl(FunctionDecl const* decl);
109 bool VisitCallExpr(CallExpr const* expr);
111 private:
112 bool rewrite(SourceLocation location);
113 bool isExcludedFile(SourceLocation spellingLocation) const;
114 /** sort by the reverse of source order, so we can do replacing from the end of the file backwards,
115 which means we reduce the chances of having overlapping changes. */
116 template <class T>
117 std::vector<std::pair<T, bool>> reverseSourceOrder(std::map<T, bool> const& map) const
119 std::vector<std::pair<T, bool>> vec(map.begin(), map.end());
120 std::sort(vec.begin(), vec.end(),
121 [&](std::pair<T, bool> const& lhs, std::pair<T, bool> const& rhs) {
122 return compiler.getSourceManager().getCharacterData(
123 compat::getBeginLoc(lhs.first))
124 > compiler.getSourceManager().getCharacterData(
125 compat::getBeginLoc(rhs.first));
127 return vec;
130 std::map<VarDecl const*, bool> varDecls_;
131 std::map<FieldDecl const*, bool> fieldDecls_;
132 std::map<ParmVarDecl const*, bool> parmVarDecls_;
133 std::map<FunctionDecl const*, bool> functionDecls_;
134 std::map<CXXStaticCastExpr const*, bool> staticCasts_;
135 std::map<CXXFunctionalCastExpr const*, bool> functionalCasts_;
138 void ToolsLong::run()
140 if (!compiler.getLangOpts().CPlusPlus)
141 return;
143 StringRef fn(handler.getMainFileName());
144 // sberg says this is fine
145 if (loplugin::isSamePathname(fn, SRCDIR "/pyuno/source/module/pyuno.cxx")
146 || loplugin::isSamePathname(fn, SRCDIR "/ucb/source/ucp/webdav-neon/NeonSession.cxx"))
147 return;
148 // these are places where the external API is actually "long"
149 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/filter/jpeg/JpegReader.cxx"))
150 return;
151 if (loplugin::isSamePathname(fn, SRCDIR "/writerperfect/source/common/DirectoryStream.cxx"))
152 return;
153 if (loplugin::isSamePathname(fn, SRCDIR "/writerperfect/source/common/WPXSvInputStream.cxx"))
154 return;
155 if (loplugin::isSamePathname(fn,
156 SRCDIR "/writerperfect/source/calc/MSWorksCalcImportFilter.cxx"))
157 return;
158 if (loplugin::isSamePathname(fn, SRCDIR "/writerperfect/qa/unit/WPXSvStreamTest.cxx"))
159 return;
160 if (loplugin::isSamePathname(fn, SRCDIR "/desktop/source/lib/init.cxx"))
161 return;
163 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
165 for (auto const& dcl : reverseSourceOrder(varDecls_))
167 auto const decl = dcl.first;
168 SourceLocation loc{ compat::getBeginLoc(decl) };
169 TypeSourceInfo* tsi = decl->getTypeSourceInfo();
170 if (tsi != nullptr)
172 SourceLocation l{ compiler.getSourceManager().getExpansionLoc(
173 tsi->getTypeLoc().getBeginLoc()) };
174 SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
175 tsi->getTypeLoc().getEndLoc()) };
176 assert(l.isFileID() && end.isFileID());
177 if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end))
179 for (;;)
181 unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
182 compiler.getLangOpts());
183 std::string s{ compiler.getSourceManager().getCharacterData(l), n };
184 if (s == "long")
186 loc = l;
187 break;
189 if (l == end)
191 break;
193 l = l.getLocWithOffset(std::max<unsigned>(n, 1));
197 if (!rewrite(loc))
199 report(DiagnosticsEngine::Warning, "VarDecl, use \"tools::Long\" instead of %0", loc)
200 << decl->getType().getLocalUnqualifiedType() << decl->getSourceRange();
203 for (auto const& dcl : reverseSourceOrder(fieldDecls_))
205 auto const decl = dcl.first;
206 SourceLocation loc{ compat::getBeginLoc(decl) };
207 TypeSourceInfo* tsi = decl->getTypeSourceInfo();
208 if (tsi != nullptr)
210 SourceLocation l{ compiler.getSourceManager().getExpansionLoc(
211 tsi->getTypeLoc().getBeginLoc()) };
212 SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
213 tsi->getTypeLoc().getEndLoc()) };
214 assert(l.isFileID() && end.isFileID());
215 if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end))
217 for (;;)
219 unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
220 compiler.getLangOpts());
221 std::string s{ compiler.getSourceManager().getCharacterData(l), n };
222 if (s == "long")
224 loc = l;
225 break;
227 if (l == end)
229 break;
231 l = l.getLocWithOffset(std::max<unsigned>(n, 1));
235 if (!rewrite(loc))
237 report(DiagnosticsEngine::Warning, "FieldDecl, use \"tools::Long\" instead of %0", loc)
238 << decl->getType().getLocalUnqualifiedType() << decl->getSourceRange();
241 for (auto const& dcl : reverseSourceOrder(parmVarDecls_))
243 auto const decl = dcl.first;
244 SourceLocation loc{ compat::getBeginLoc(decl) };
245 TypeSourceInfo* tsi = decl->getTypeSourceInfo();
246 if (tsi != nullptr)
248 SourceLocation l{ compiler.getSourceManager().getExpansionLoc(
249 tsi->getTypeLoc().getBeginLoc()) };
250 SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
251 tsi->getTypeLoc().getEndLoc()) };
252 assert(l.isFileID() && end.isFileID());
253 if (l == end || (compiler.getSourceManager().isBeforeInTranslationUnit(l, end)))
255 for (;;)
257 unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
258 compiler.getLangOpts());
259 std::string s{ compiler.getSourceManager().getCharacterData(l), n };
260 if (s == "long")
262 loc = l;
263 break;
265 if (l == end)
267 break;
269 l = l.getLocWithOffset(std::max<unsigned>(n, 1));
273 FunctionDecl const* f = dyn_cast_or_null<FunctionDecl>(decl->getDeclContext());
274 if (f)
275 f = f->getCanonicalDecl();
276 OverrideKind k = f ? getOverrideKind(f) : OverrideKind::NO;
277 if (k == OverrideKind::MAYBE || !rewrite(loc))
279 report(DiagnosticsEngine::Warning,
280 ("ParmVarDecl, use \"tools::Long\" instead of"
281 " %0%1"),
282 loc)
283 << decl->getType().getNonReferenceType().getLocalUnqualifiedType()
284 << (k == OverrideKind::MAYBE ? (" (unless this member function overrides a"
285 " dependent base member function, even"
286 " though it is not marked 'override')")
287 : "")
288 << decl->getSourceRange();
291 for (auto const& dcl : functionDecls_)
293 auto const decl = dcl.first;
294 SourceLocation loc{ compat::getBeginLoc(decl) };
295 SourceLocation l{ compiler.getSourceManager().getExpansionLoc(loc) };
296 SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
297 decl->getNameInfo().getLoc()) };
298 assert(l.isFileID() && end.isFileID());
299 if (compiler.getSourceManager().isBeforeInTranslationUnit(l, end))
301 while (l != end)
303 unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
304 compiler.getLangOpts());
305 std::string s{ compiler.getSourceManager().getCharacterData(l), n };
306 if (s == "long")
308 loc = l;
309 break;
311 l = l.getLocWithOffset(std::max<unsigned>(n, 1));
314 if (rewrite(loc))
315 continue;
316 report(DiagnosticsEngine::Warning, "use \"tools::Long\" instead of %0 as return type%1",
317 loc)
318 << decl->getReturnType().getNonReferenceType().getLocalUnqualifiedType()
319 << (getOverrideKind(decl) == OverrideKind::MAYBE
320 ? (" (unless this member function overrides a dependent"
321 " base member function, even though it is not marked"
322 " 'override')")
323 : "")
324 << decl->getSourceRange();
327 for (auto const& dcl : staticCasts_)
329 auto const expr = dcl.first;
330 SourceLocation loc{ compat::getBeginLoc(expr) };
331 TypeSourceInfo* tsi = expr->getTypeInfoAsWritten();
332 if (tsi != nullptr)
334 SourceLocation l{ compiler.getSourceManager().getExpansionLoc(
335 tsi->getTypeLoc().getBeginLoc()) };
336 SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
337 tsi->getTypeLoc().getEndLoc()) };
338 assert(l.isFileID() && end.isFileID());
339 if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end))
341 for (;;)
343 unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
344 compiler.getLangOpts());
345 std::string s{ compiler.getSourceManager().getCharacterData(l), n };
346 if (s == "long")
348 loc = l;
349 break;
351 if (l == end)
353 break;
355 l = l.getLocWithOffset(std::max<unsigned>(n, 1));
359 if (!rewrite(loc))
361 report(DiagnosticsEngine::Warning, "CXXStaticCastExpr, suspicious cast from %0 to %1",
362 compat::getBeginLoc(expr))
363 << expr->getSubExpr()->IgnoreParenImpCasts()->getType() << expr->getType()
364 << expr->getSourceRange();
368 for (auto const& dcl : functionalCasts_)
370 auto const expr = dcl.first;
371 SourceLocation loc{ compat::getBeginLoc(expr) };
372 TypeSourceInfo* tsi = expr->getTypeInfoAsWritten();
373 if (tsi != nullptr)
375 SourceLocation l{ compiler.getSourceManager().getExpansionLoc(
376 tsi->getTypeLoc().getBeginLoc()) };
377 SourceLocation end{ compiler.getSourceManager().getExpansionLoc(
378 tsi->getTypeLoc().getEndLoc()) };
379 assert(l.isFileID() && end.isFileID());
380 if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end))
382 for (;;)
384 unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(),
385 compiler.getLangOpts());
386 std::string s{ compiler.getSourceManager().getCharacterData(l), n };
387 if (s == "long")
389 loc = l;
390 break;
392 if (l == end)
394 break;
396 l = l.getLocWithOffset(std::max<unsigned>(n, 1));
400 if (!rewrite(loc))
402 report(DiagnosticsEngine::Warning,
403 "CXXFunctionalCastExpr, suspicious cast from %0 to %1",
404 compat::getBeginLoc(expr))
405 << expr->getSubExpr()->IgnoreParenImpCasts()->getType() << expr->getType()
406 << expr->getSourceRange();
411 bool ToolsLong::VisitCStyleCastExpr(CStyleCastExpr* expr)
413 if (ignoreLocation(expr))
414 return true;
415 if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr))))
416 return true;
417 auto const k = isLong(expr->getType());
418 if (!k)
419 return true;
420 SourceLocation loc{ compat::getBeginLoc(expr) };
421 while (compiler.getSourceManager().isMacroArgExpansion(loc))
422 loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
423 report(DiagnosticsEngine::Warning, "CStyleCastExpr, suspicious cast from %0 to %1",
424 compat::getBeginLoc(expr))
425 << expr->getSubExpr()->IgnoreParenImpCasts()->getType() << expr->getType()
426 << expr->getSourceRange();
427 return true;
430 bool ToolsLong::VisitCXXStaticCastExpr(CXXStaticCastExpr* expr)
432 if (ignoreLocation(expr))
433 return true;
434 if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr))))
435 return true;
436 auto const k = isLong(expr->getType());
437 if (!k)
438 return true;
439 staticCasts_.insert({ expr, k });
440 return true;
443 bool ToolsLong::VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr)
445 if (ignoreLocation(expr))
446 return true;
447 if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr))))
448 return true;
449 auto const k = isLong(expr->getType());
450 if (!k)
451 return true;
452 functionalCasts_.insert({ expr, k });
453 return true;
456 bool ToolsLong::WalkUpFromParmVarDecl(ParmVarDecl const* decl) { return VisitParmVarDecl(decl); }
458 bool ToolsLong::VisitParmVarDecl(ParmVarDecl const* decl)
460 if (ignoreLocation(decl))
461 return true;
462 if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
463 return true;
464 auto const fbk = isLong(decl->getType());
465 if (!fbk)
466 return true;
467 FunctionDecl const* f = dyn_cast<FunctionDecl>(decl->getDeclContext());
468 if (f) // e.g.: typedef sal_Bool (* FuncPtr )( sal_Bool );
470 // ignore the function in include/test/cppunitasserthelper.hxx
471 if (f->getIdentifier() && f->getName() == "assertEquals")
472 return true;
473 auto canonicalF = f->getCanonicalDecl();
474 if (canonicalF->isDeletedAsWritten() && isa<CXXConversionDecl>(canonicalF))
475 return true;
476 // Only rewrite declarations in include files if a definition is
477 // also seen, to avoid compilation of a definition (in a main file
478 // only processed later) to fail with a "mismatch" error before the
479 // rewriter had a chance to act upon the definition (but use the
480 // heuristic of assuming pure virtual functions do not have
481 // definitions):
482 bool ok = canonicalF->isDefined() || canonicalF->isPure()
483 || compiler.getSourceManager().isInMainFile(
484 compiler.getSourceManager().getSpellingLoc(f->getNameInfo().getLoc()));
485 if (!ok)
486 return true;
488 parmVarDecls_.insert({ decl, fbk });
489 return true;
492 bool ToolsLong::WalkUpFromVarDecl(VarDecl const* decl) { return VisitVarDecl(decl); }
494 bool ToolsLong::VisitVarDecl(VarDecl const* decl)
496 if (ignoreLocation(decl))
497 return true;
498 if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
499 return true;
500 auto k = isLong(decl->getType());
501 if (!k)
502 return true;
503 varDecls_.insert({ decl, k });
504 return true;
507 bool ToolsLong::WalkUpFromFieldDecl(FieldDecl const* decl) { return VisitFieldDecl(decl); }
509 bool ToolsLong::VisitFieldDecl(FieldDecl const* decl)
511 if (ignoreLocation(decl))
512 return true;
513 if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
514 return true;
515 auto k = isLong(decl->getType());
516 if (!k)
517 return true;
518 TagDecl const* td = dyn_cast<TagDecl>(decl->getDeclContext());
519 if (td == nullptr)
521 //TODO: ObjCInterface
522 return true;
524 fieldDecls_.insert({ decl, k });
525 return true;
528 bool ToolsLong::WalkUpFromFunctionDecl(FunctionDecl const* decl) { return VisitFunctionDecl(decl); }
530 bool ToolsLong::VisitFunctionDecl(FunctionDecl const* decl)
532 if (ignoreLocation(decl))
533 return true;
534 if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
535 return true;
536 auto const fbk = isLong(decl->getReturnType());
537 if (!fbk)
538 return true;
539 if (decl->isDeletedAsWritten() && isa<CXXConversionDecl>(decl))
540 return true;
541 if (decl->isPure() || decl->isDefined()
542 || compiler.getSourceManager().isInMainFile(
543 compiler.getSourceManager().getSpellingLoc(decl->getNameInfo().getLoc())))
545 functionDecls_.insert({ decl, fbk });
547 return true;
550 bool ToolsLong::VisitCallExpr(CallExpr const* expr)
552 if (ignoreLocation(expr))
554 return true;
556 auto const d1 = expr->getDirectCallee();
557 if (d1 == nullptr || !loplugin::DeclCheck(d1).Function("curl_easy_getinfo").GlobalNamespace())
559 return true;
561 if (expr->getNumArgs() != 3)
563 return true;
565 //TODO: Check expr->getArg(1) is CURLINFO_RESPONSE_CODE
566 auto const e1 = dyn_cast<UnaryOperator>(expr->getArg(2)->IgnoreParenImpCasts());
567 if (e1 == nullptr || e1->getOpcode() != UO_AddrOf)
569 return true;
571 auto const e2 = dyn_cast<DeclRefExpr>(e1->getSubExpr()->IgnoreParenImpCasts());
572 if (e2 == nullptr)
574 return true;
576 auto const d2 = e2->getDecl();
577 if (auto const d3 = dyn_cast<ParmVarDecl>(d2))
579 parmVarDecls_.erase(d3);
581 else if (auto const d4 = dyn_cast<VarDecl>(d2))
583 varDecls_.erase(d4);
585 else if (auto const d5 = dyn_cast<FieldDecl>(d2))
587 fieldDecls_.erase(d5);
589 return true;
592 bool ToolsLong::rewrite(SourceLocation location)
594 if (rewriter != nullptr)
596 SourceLocation loc{ compiler.getSourceManager().getExpansionLoc(location) };
597 unsigned n
598 = Lexer::MeasureTokenLength(loc, compiler.getSourceManager(), compiler.getLangOpts());
599 if (std::string(compiler.getSourceManager().getCharacterData(loc), n) == "long")
601 return replaceText(loc, n, "tools::Long");
604 return false;
607 bool ToolsLong::isExcludedFile(SourceLocation spellingLocation) const
609 if (isInUnoIncludeFile(spellingLocation))
610 return true;
611 auto f = getFilenameOfLocation(spellingLocation);
612 return loplugin::hasPathnamePrefix(f, SRCDIR "/include/cppu/")
613 || loplugin::hasPathnamePrefix(f, SRCDIR "/include/cppuhelper/")
614 || loplugin::hasPathnamePrefix(f, SRCDIR "/include/registry/")
615 || loplugin::hasPathnamePrefix(f, SRCDIR "/include/osl/")
616 || loplugin::hasPathnamePrefix(f, SRCDIR "/include/rtl/")
617 || loplugin::hasPathnamePrefix(f, SRCDIR "/include/sal/")
618 || loplugin::hasPathnamePrefix(f, SRCDIR "/include/salhelper/")
619 || loplugin::hasPathnamePrefix(f, SRCDIR "/include/typelib/")
620 || loplugin::hasPathnamePrefix(f, SRCDIR "/include/LibreOfficeKit/") // TODO
621 || loplugin::hasPathnamePrefix(f, SRCDIR "/bridges/")
622 || loplugin::hasPathnamePrefix(f, SRCDIR "/codemaker/")
623 || loplugin::hasPathnamePrefix(f, SRCDIR "/configmgr/")
624 || loplugin::hasPathnamePrefix(f, SRCDIR "/cppu/")
625 || loplugin::hasPathnamePrefix(f, SRCDIR "/cppuhelper/")
626 || loplugin::hasPathnamePrefix(f, SRCDIR "/external/")
627 || loplugin::hasPathnamePrefix(f, SRCDIR "/libreofficekit/") // TODO
628 || loplugin::hasPathnamePrefix(f, SRCDIR "/registry/")
629 || loplugin::hasPathnamePrefix(f, SRCDIR "/rtl/")
630 || loplugin::hasPathnamePrefix(f, SRCDIR "/sal/")
631 || loplugin::hasPathnamePrefix(f, SRCDIR "/salhelper/")
632 || loplugin::hasPathnamePrefix(f, SRCDIR "/soltools/")
633 || loplugin::hasPathnamePrefix(f, SRCDIR "/unoidl/")
634 || loplugin::hasPathnamePrefix(f, SRCDIR "/workdir/");
637 loplugin::Plugin::Registration<ToolsLong> X("toolslong", true);
640 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */