1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
17 #include "clang/AST/Attr.h"
18 #include "clang/Basic/Builtins.h"
20 #include "config_clang.h"
28 bool isLong(QualType type
)
30 type
= type
.getNonReferenceType();
32 if (type
->getAs
<TypedefType
>())
34 // some parts of the STL have ::difference_type => long
35 if (type
->getAs
<AutoType
>() || type
->getAs
<DecltypeType
>())
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
>())
48 return isLong(t
->getAliasedType());
52 if (type
->isSpecificBuiltinType(BuiltinType::Kind::Long
))
54 auto arrayType
= type
->getAsArrayTypeUnsafe();
56 return isLong(arrayType
->getElementType());
57 if (type
->isPointerType())
58 return isLong(type
->getPointeeType());
62 enum class OverrideKind
69 OverrideKind
getOverrideKind(FunctionDecl
const* decl
)
71 CXXMethodDecl
const* m
= dyn_cast
<CXXMethodDecl
>(decl
);
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
>
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
);
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. */
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
));
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
)
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"))
148 // these are places where the external API is actually "long"
149 if (loplugin::isSamePathname(fn
, SRCDIR
"/vcl/source/filter/jpeg/JpegReader.cxx"))
151 if (loplugin::isSamePathname(fn
, SRCDIR
"/writerperfect/source/common/DirectoryStream.cxx"))
153 if (loplugin::isSamePathname(fn
, SRCDIR
"/writerperfect/source/common/WPXSvInputStream.cxx"))
155 if (loplugin::isSamePathname(fn
,
156 SRCDIR
"/writerperfect/source/calc/MSWorksCalcImportFilter.cxx"))
158 if (loplugin::isSamePathname(fn
, SRCDIR
"/writerperfect/qa/unit/WPXSvStreamTest.cxx"))
160 if (loplugin::isSamePathname(fn
, SRCDIR
"/desktop/source/lib/init.cxx"))
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();
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
))
181 unsigned n
= Lexer::MeasureTokenLength(l
, compiler
.getSourceManager(),
182 compiler
.getLangOpts());
183 std::string s
{ compiler
.getSourceManager().getCharacterData(l
), n
};
193 l
= l
.getLocWithOffset(std::max
<unsigned>(n
, 1));
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();
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
))
219 unsigned n
= Lexer::MeasureTokenLength(l
, compiler
.getSourceManager(),
220 compiler
.getLangOpts());
221 std::string s
{ compiler
.getSourceManager().getCharacterData(l
), n
};
231 l
= l
.getLocWithOffset(std::max
<unsigned>(n
, 1));
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();
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
)))
257 unsigned n
= Lexer::MeasureTokenLength(l
, compiler
.getSourceManager(),
258 compiler
.getLangOpts());
259 std::string s
{ compiler
.getSourceManager().getCharacterData(l
), n
};
269 l
= l
.getLocWithOffset(std::max
<unsigned>(n
, 1));
273 FunctionDecl
const* f
= dyn_cast_or_null
<FunctionDecl
>(decl
->getDeclContext());
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"
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')")
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
))
303 unsigned n
= Lexer::MeasureTokenLength(l
, compiler
.getSourceManager(),
304 compiler
.getLangOpts());
305 std::string s
{ compiler
.getSourceManager().getCharacterData(l
), n
};
311 l
= l
.getLocWithOffset(std::max
<unsigned>(n
, 1));
316 report(DiagnosticsEngine::Warning
, "use \"tools::Long\" instead of %0 as return type%1",
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"
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();
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
))
343 unsigned n
= Lexer::MeasureTokenLength(l
, compiler
.getSourceManager(),
344 compiler
.getLangOpts());
345 std::string s
{ compiler
.getSourceManager().getCharacterData(l
), n
};
355 l
= l
.getLocWithOffset(std::max
<unsigned>(n
, 1));
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();
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
))
384 unsigned n
= Lexer::MeasureTokenLength(l
, compiler
.getSourceManager(),
385 compiler
.getLangOpts());
386 std::string s
{ compiler
.getSourceManager().getCharacterData(l
), n
};
396 l
= l
.getLocWithOffset(std::max
<unsigned>(n
, 1));
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
))
415 if (isExcludedFile(compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr
))))
417 auto const k
= isLong(expr
->getType());
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();
430 bool ToolsLong::VisitCXXStaticCastExpr(CXXStaticCastExpr
* expr
)
432 if (ignoreLocation(expr
))
434 if (isExcludedFile(compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr
))))
436 auto const k
= isLong(expr
->getType());
439 staticCasts_
.insert({ expr
, k
});
443 bool ToolsLong::VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr
* expr
)
445 if (ignoreLocation(expr
))
447 if (isExcludedFile(compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr
))))
449 auto const k
= isLong(expr
->getType());
452 functionalCasts_
.insert({ expr
, k
});
456 bool ToolsLong::WalkUpFromParmVarDecl(ParmVarDecl
const* decl
) { return VisitParmVarDecl(decl
); }
458 bool ToolsLong::VisitParmVarDecl(ParmVarDecl
const* decl
)
460 if (ignoreLocation(decl
))
462 if (isExcludedFile(compiler
.getSourceManager().getSpellingLoc(decl
->getLocation())))
464 auto const fbk
= isLong(decl
->getType());
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")
473 auto canonicalF
= f
->getCanonicalDecl();
474 if (canonicalF
->isDeletedAsWritten() && isa
<CXXConversionDecl
>(canonicalF
))
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
482 bool ok
= canonicalF
->isDefined() || canonicalF
->isPure()
483 || compiler
.getSourceManager().isInMainFile(
484 compiler
.getSourceManager().getSpellingLoc(f
->getNameInfo().getLoc()));
488 parmVarDecls_
.insert({ decl
, fbk
});
492 bool ToolsLong::WalkUpFromVarDecl(VarDecl
const* decl
) { return VisitVarDecl(decl
); }
494 bool ToolsLong::VisitVarDecl(VarDecl
const* decl
)
496 if (ignoreLocation(decl
))
498 if (isExcludedFile(compiler
.getSourceManager().getSpellingLoc(decl
->getLocation())))
500 auto k
= isLong(decl
->getType());
503 varDecls_
.insert({ decl
, k
});
507 bool ToolsLong::WalkUpFromFieldDecl(FieldDecl
const* decl
) { return VisitFieldDecl(decl
); }
509 bool ToolsLong::VisitFieldDecl(FieldDecl
const* decl
)
511 if (ignoreLocation(decl
))
513 if (isExcludedFile(compiler
.getSourceManager().getSpellingLoc(decl
->getLocation())))
515 auto k
= isLong(decl
->getType());
518 TagDecl
const* td
= dyn_cast
<TagDecl
>(decl
->getDeclContext());
521 //TODO: ObjCInterface
524 fieldDecls_
.insert({ decl
, k
});
528 bool ToolsLong::WalkUpFromFunctionDecl(FunctionDecl
const* decl
) { return VisitFunctionDecl(decl
); }
530 bool ToolsLong::VisitFunctionDecl(FunctionDecl
const* decl
)
532 if (ignoreLocation(decl
))
534 if (isExcludedFile(compiler
.getSourceManager().getSpellingLoc(decl
->getLocation())))
536 auto const fbk
= isLong(decl
->getReturnType());
539 if (decl
->isDeletedAsWritten() && isa
<CXXConversionDecl
>(decl
))
541 if (decl
->isPure() || decl
->isDefined()
542 || compiler
.getSourceManager().isInMainFile(
543 compiler
.getSourceManager().getSpellingLoc(decl
->getNameInfo().getLoc())))
545 functionDecls_
.insert({ decl
, fbk
});
550 bool ToolsLong::VisitCallExpr(CallExpr
const* expr
)
552 if (ignoreLocation(expr
))
556 auto const d1
= expr
->getDirectCallee();
557 if (d1
== nullptr || !loplugin::DeclCheck(d1
).Function("curl_easy_getinfo").GlobalNamespace())
561 if (expr
->getNumArgs() != 3)
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
)
571 auto const e2
= dyn_cast
<DeclRefExpr
>(e1
->getSubExpr()->IgnoreParenImpCasts());
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
))
585 else if (auto const d5
= dyn_cast
<FieldDecl
>(d2
))
587 fieldDecls_
.erase(d5
);
592 bool ToolsLong::rewrite(SourceLocation location
)
594 if (rewriter
!= nullptr)
596 SourceLocation loc
{ compiler
.getSourceManager().getExpansionLoc(location
) };
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");
607 bool ToolsLong::isExcludedFile(SourceLocation spellingLocation
) const
609 if (isInUnoIncludeFile(spellingLocation
))
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: */