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/.
20 // We don't like using C-style casts in C++ code
25 bool areSimilar(QualType type1
, QualType type2
) {
26 auto t1
= type1
.getCanonicalType().getTypePtr();
27 auto t2
= type2
.getCanonicalType().getTypePtr();
29 if (t1
->isPointerType()) {
30 if (!t2
->isPointerType()) {
33 auto t1a
= t1
->getAs
<clang::PointerType
>();
34 auto t2a
= t2
->getAs
<clang::PointerType
>();
35 t1
= t1a
->getPointeeType().getTypePtr();
36 t2
= t2a
->getPointeeType().getTypePtr();
37 } else if (t1
->isMemberPointerType()) {
38 if (!t2
->isMemberPointerType()) {
41 auto t1a
= t1
->getAs
<MemberPointerType
>();
42 auto t2a
= t2
->getAs
<MemberPointerType
>();
43 if (t1a
->getClass()->getCanonicalTypeInternal()
44 != t2a
->getClass()->getCanonicalTypeInternal())
48 t1
= t1a
->getPointeeType().getTypePtr();
49 t2
= t2a
->getPointeeType().getTypePtr();
50 } else if (t1
->isConstantArrayType()) {
51 if (!t2
->isConstantArrayType()) {
54 auto t1a
= static_cast<ConstantArrayType
const *>(
55 t1
->getAsArrayTypeUnsafe());
56 auto t2a
= static_cast<ConstantArrayType
const *>(
57 t2
->getAsArrayTypeUnsafe());
58 if (t1a
->getSize() != t2a
->getSize()) {
61 t1
= t1a
->getElementType().getTypePtr();
62 t2
= t2a
->getElementType().getTypePtr();
63 } else if (t1
->isIncompleteArrayType()) {
64 if (!t2
->isIncompleteArrayType()) {
67 auto t1a
= static_cast<IncompleteArrayType
const *>(
68 t1
->getAsArrayTypeUnsafe());
69 auto t2a
= static_cast<IncompleteArrayType
const *>(
70 t2
->getAsArrayTypeUnsafe());
71 t1
= t1a
->getElementType().getTypePtr();
72 t2
= t2a
->getElementType().getTypePtr();
82 QualType
resolvePointers(QualType type
) {
83 while (type
->isPointerType()) {
84 type
= type
->getAs
<clang::PointerType
>()->getPointeeType();
89 bool isLiteralLike(Expr
const * expr
) {
90 expr
= expr
->IgnoreParenImpCasts();
91 if (isa
<IntegerLiteral
>(expr
) || isa
<CharacterLiteral
>(expr
) || isa
<FloatingLiteral
>(expr
)
92 || isa
<ImaginaryLiteral
>(expr
) || isa
<CXXBoolLiteralExpr
>(expr
)
93 || isa
<CXXNullPtrLiteralExpr
>(expr
) || isa
<ObjCBoolLiteralExpr
>(expr
))
97 if (auto const e
= dyn_cast
<DeclRefExpr
>(expr
)) {
98 auto const d
= e
->getDecl();
99 if (isa
<EnumConstantDecl
>(d
)) {
102 if (auto const v
= dyn_cast
<VarDecl
>(d
)) {
103 if (d
->getType().isConstQualified()) {
104 if (auto const init
= v
->getAnyInitializer()) {
105 return isLiteralLike(init
);
111 if (auto const e
= dyn_cast
<UnaryExprOrTypeTraitExpr
>(expr
)) {
112 auto const k
= e
->getKind();
113 return k
== UETT_SizeOf
|| k
== UETT_AlignOf
;
115 if (auto const e
= dyn_cast
<UnaryOperator
>(expr
)) {
116 auto const k
= e
->getOpcode();
117 if (k
== UO_Plus
|| k
== UO_Minus
|| k
== UO_Not
|| k
== UO_LNot
) {
118 return isLiteralLike(e
->getSubExpr());
122 if (auto const e
= dyn_cast
<BinaryOperator
>(expr
)) {
123 auto const k
= e
->getOpcode();
124 if (k
== BO_Mul
|| k
== BO_Div
|| k
== BO_Rem
|| k
== BO_Add
|| k
== BO_Sub
|| k
== BO_Shl
125 || k
== BO_Shr
|| k
== BO_And
|| k
== BO_Xor
|| k
== BO_Or
)
127 return isLiteralLike(e
->getLHS()) && isLiteralLike(e
->getRHS());
131 if (auto const e
= dyn_cast
<ExplicitCastExpr
>(expr
)) {
132 auto const t
= e
->getTypeAsWritten();
133 return (t
->isArithmeticType() || t
->isEnumeralType())
134 && isLiteralLike(e
->getSubExprAsWritten());
139 bool canBeUsedForFunctionalCast(TypeSourceInfo
const * info
) {
140 // Must be <simple-type-specifier> or <typename-specifier>, lets approximate that here:
141 assert(info
!= nullptr);
142 auto const type
= info
->getType();
143 if (type
.hasLocalQualifiers()) {
146 if (auto const t
= dyn_cast
<BuiltinType
>(type
)) {
147 if (!(t
->isInteger() || t
->isFloatingPoint())) {
150 auto const loc
= info
->getTypeLoc().castAs
<BuiltinTypeLoc
>();
152 (int(loc
.hasWrittenSignSpec()) + int(loc
.hasWrittenWidthSpec())
153 + int(loc
.hasWrittenTypeSpec()))
156 if (isa
<TagType
>(type
) || isa
<TemplateTypeParmType
>(type
) || isa
<AutoType
>(type
)
157 || isa
<DecltypeType
>(type
) || isa
<TypedefType
>(type
))
161 if (auto const t
= dyn_cast
<ElaboratedType
>(type
)) {
162 return t
->getKeyword() == ETK_None
;
168 public loplugin::FilteringRewritePlugin
<CStyleCast
>
171 explicit CStyleCast(loplugin::InstantiationData
const & data
): FilteringRewritePlugin(data
)
174 virtual void run() override
{
175 if (compiler
.getLangOpts().CPlusPlus
) {
176 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
180 bool TraverseInitListExpr(InitListExpr
* expr
, DataRecursionQueue
* queue
= nullptr) {
181 return WalkUpFromInitListExpr(expr
)
182 && TraverseSynOrSemInitListExpr(
183 expr
->isSemanticForm() ? expr
: expr
->getSemanticForm(), queue
);
186 bool TraverseLinkageSpecDecl(LinkageSpecDecl
* decl
);
188 bool VisitCStyleCastExpr(const CStyleCastExpr
* expr
);
191 bool isConstCast(QualType from
, QualType to
);
193 bool isFromCIncludeFile(SourceLocation spellingLocation
) const;
195 bool isSharedCAndCppCode(SourceLocation location
) const;
197 bool isLastTokenOfImmediateMacroBodyExpansion(
198 SourceLocation loc
, SourceLocation
* macroEnd
= nullptr) const;
200 bool rewriteArithmeticCast(CStyleCastExpr
const * expr
, char const ** replacement
);
202 unsigned int externCContexts_
= 0;
203 std::set
<SourceLocation
> rewritten_
;
204 // needed when rewriting in macros, in general to avoid "double code replacement, possible
205 // plugin error" warnings, and in particular to avoid adding multiple sets of parens around
207 std::set
<CStyleCastExpr
const *> rewrittenSubExprs_
;
210 const char * recommendedFix(clang::CastKind ck
) {
212 case CK_IntegralToPointer
: return "reinterpret_cast";
213 case CK_PointerToIntegral
: return "reinterpret_cast";
214 case CK_BaseToDerived
: return "static_cast";
215 default: return nullptr;
219 bool CStyleCast::TraverseLinkageSpecDecl(LinkageSpecDecl
* decl
) {
220 assert(externCContexts_
!= std::numeric_limits
<unsigned int>::max()); //TODO
222 bool ret
= RecursiveASTVisitor::TraverseLinkageSpecDecl(decl
);
223 assert(externCContexts_
!= 0);
228 bool CStyleCast::VisitCStyleCastExpr(const CStyleCastExpr
* expr
) {
229 if (ignoreLocation(expr
)) {
232 // casting to void is typically used when a parameter or field is only used in
233 // debug mode, and we want to eliminate an "unused" warning
234 if( expr
->getCastKind() == CK_ToVoid
) {
237 if (isSharedCAndCppCode(compat::getBeginLoc(expr
))) {
240 char const * perf
= nullptr;
241 if( expr
->getCastKind() == CK_IntegralCast
) {
242 if (rewriteArithmeticCast(expr
, &perf
)) {
245 } else if( expr
->getCastKind() == CK_NoOp
) {
246 if (!((expr
->getSubExpr()->getType()->isPointerType()
247 && expr
->getType()->isPointerType())
248 || expr
->getTypeAsWritten()->isReferenceType()))
250 if (rewriteArithmeticCast(expr
, &perf
)) {
255 expr
->getSubExprAsWritten()->getType(),
256 expr
->getTypeAsWritten()))
261 std::string incompFrom
;
262 std::string incompTo
;
263 if( expr
->getCastKind() == CK_BitCast
) {
264 if (resolvePointers(expr
->getSubExprAsWritten()->getType())
265 ->isIncompleteType())
267 incompFrom
= "incomplete ";
269 if (resolvePointers(expr
->getType())->isIncompleteType()) {
270 incompTo
= "incomplete ";
273 if (perf
== nullptr) {
274 perf
= recommendedFix(expr
->getCastKind());
276 std::string performs
;
277 if (perf
!= nullptr) {
278 performs
= std::string(" (performs: ") + perf
+ ")";
281 DiagnosticsEngine::Warning
, "C-style cast from %0%1 to %2%3%4 (%5)",
282 expr
->getSourceRange().getBegin())
283 << incompFrom
<< expr
->getSubExprAsWritten()->getType()
284 << incompTo
<< expr
->getTypeAsWritten() << performs
285 << expr
->getCastKindName()
286 << expr
->getSourceRange();
290 bool CStyleCast::isConstCast(QualType from
, QualType to
) {
291 if (to
->isReferenceType()
292 && to
->getAs
<ReferenceType
>()->getPointeeType()->isObjectType())
294 if (!from
->isObjectType()) {
297 from
= compiler
.getASTContext().getPointerType(from
);
298 to
= compiler
.getASTContext().getPointerType(
299 to
->getAs
<ReferenceType
>()->getPointeeType());
301 if (from
->isArrayType()) {
302 from
= compiler
.getASTContext().getPointerType(
303 from
->getAsArrayTypeUnsafe()->getElementType());
304 } else if (from
->isFunctionType()) {
305 compiler
.getASTContext().getPointerType(from
);
308 return areSimilar(from
, to
);
311 bool CStyleCast::isFromCIncludeFile(SourceLocation spellingLocation
) const {
312 return !compiler
.getSourceManager().isInMainFile(spellingLocation
)
314 compiler
.getSourceManager().getPresumedLoc(spellingLocation
)
319 bool CStyleCast::isSharedCAndCppCode(SourceLocation location
) const {
320 while (compiler
.getSourceManager().isMacroArgExpansion(location
)) {
321 location
= compiler
.getSourceManager().getImmediateMacroCallerLoc(
324 // Assume that code is intended to be shared between C and C++ if it comes
325 // from an include file ending in .h, and is either in an extern "C" context
326 // or the body of a macro definition:
328 isFromCIncludeFile(compiler
.getSourceManager().getSpellingLoc(location
))
329 && (externCContexts_
!= 0
330 || compiler
.getSourceManager().isMacroBodyExpansion(location
));
333 bool CStyleCast::isLastTokenOfImmediateMacroBodyExpansion(
334 SourceLocation loc
, SourceLocation
* macroEnd
) const
336 assert(compiler
.getSourceManager().isMacroBodyExpansion(loc
));
337 auto const spell
= compiler
.getSourceManager().getSpellingLoc(loc
);
338 auto name
= Lexer::getImmediateMacroName(
339 loc
, compiler
.getSourceManager(), compiler
.getLangOpts());
340 while (name
.startswith("\\\n")) {
341 name
= name
.drop_front(2);
343 && (name
.front() == ' ' || name
.front() == '\t' || name
.front() == '\n'
344 || name
.front() == '\v' || name
.front() == '\f'))
346 name
= name
.drop_front(1);
350 = (compiler
.getPreprocessor().getMacroDefinitionAtLoc(
351 &compiler
.getASTContext().Idents
.get(name
), spell
)
353 assert(MI
!= nullptr);
354 if (spell
== MI
->getDefinitionEndLoc()) {
355 if (macroEnd
!= nullptr) {
356 *macroEnd
= compat::getImmediateExpansionRange(compiler
.getSourceManager(), loc
).second
;
363 bool CStyleCast::rewriteArithmeticCast(CStyleCastExpr
const * expr
, char const ** replacement
) {
364 assert(replacement
!= nullptr);
365 auto const sub
= expr
->getSubExprAsWritten();
366 auto const functional
= isLiteralLike(sub
)
367 && canBeUsedForFunctionalCast(expr
->getTypeInfoAsWritten());
368 *replacement
= functional
? "functional cast" : "static_cast";
369 if (rewriter
== nullptr) {
372 // Doing modifications for a chain of C-style casts as in
376 // leads to unpredictable results, so only rewrite them one at a time, starting with the
378 if (auto const e
= dyn_cast
<CStyleCastExpr
>(sub
)) {
379 rewrittenSubExprs_
.insert(e
);
381 if (rewrittenSubExprs_
.find(expr
) != rewrittenSubExprs_
.end()) {
384 // Two or four ranges to replace:
385 // First is the CStyleCast's LParen, plus following whitespace, replaced with either "" or
386 // "static_cast<". (TODO: insert space before "static_cast<" when converting "else(int)...".)
387 // Second is the CStyleCast's RParen, plus preceding and following whitespace, replaced with
389 // If the sub expr is not a ParenExpr, third is the sub expr's begin, inserting "(", and fourth
390 // is the sub expr's end, inserting ")".
391 // (The reason the second and third are not combined is in case there's a comment between them.)
392 auto firstBegin
= expr
->getLParenLoc();
393 auto secondBegin
= expr
->getRParenLoc();
394 while (compiler
.getSourceManager().isMacroArgExpansion(firstBegin
)
395 && compiler
.getSourceManager().isMacroArgExpansion(secondBegin
)
396 && (compat::getImmediateExpansionRange(compiler
.getSourceManager(), firstBegin
)
397 == compat::getImmediateExpansionRange(compiler
.getSourceManager(), secondBegin
)))
399 firstBegin
= compiler
.getSourceManager().getImmediateSpellingLoc(firstBegin
);
400 secondBegin
= compiler
.getSourceManager().getImmediateSpellingLoc(secondBegin
);
402 if (compiler
.getSourceManager().isMacroBodyExpansion(firstBegin
)
403 && compiler
.getSourceManager().isMacroBodyExpansion(secondBegin
)
404 && (compiler
.getSourceManager().getImmediateMacroCallerLoc(firstBegin
)
405 == compiler
.getSourceManager().getImmediateMacroCallerLoc(secondBegin
)))
407 firstBegin
= compiler
.getSourceManager().getSpellingLoc(firstBegin
);
408 secondBegin
= compiler
.getSourceManager().getSpellingLoc(secondBegin
);
410 auto third
= compat::getBeginLoc(sub
);
411 auto fourth
= compat::getEndLoc(sub
);
415 // #define FOO(x) (int)x
420 // #define FOO(x) static_cast<int>(x)
425 // #define FOO(x) static_cast<int>x
427 while (compiler
.getSourceManager().isMacroArgExpansion(third
)
428 && compiler
.getSourceManager().isMacroArgExpansion(fourth
)
429 && (compat::getImmediateExpansionRange(compiler
.getSourceManager(), third
)
430 == compat::getImmediateExpansionRange(compiler
.getSourceManager(), fourth
))
431 && compiler
.getSourceManager().isAtStartOfImmediateMacroExpansion(third
))
432 //TODO: check fourth is at end of immediate macro expansion, but
433 // SourceManager::isAtEndOfImmediateMacroExpansion requires a location pointing at the
434 // character end of the last token
436 auto const range
= compat::getImmediateExpansionRange(compiler
.getSourceManager(), third
);
438 fourth
= range
.second
;
440 assert(third
.isValid());
442 while (compiler
.getSourceManager().isMacroArgExpansion(third
)
443 && compiler
.getSourceManager().isMacroArgExpansion(fourth
)
444 && (compat::getImmediateExpansionRange(compiler
.getSourceManager(), third
)
445 == compat::getImmediateExpansionRange(compiler
.getSourceManager(), fourth
)))
447 third
= compiler
.getSourceManager().getImmediateSpellingLoc(third
);
448 fourth
= compiler
.getSourceManager().getImmediateSpellingLoc(fourth
);
450 if (isa
<ParenExpr
>(sub
)) {
461 // static_cast<int>(FOO)
465 // static_cast<int>FOO
466 for (;; macro
= true) {
467 if (!(compiler
.getSourceManager().isMacroBodyExpansion(third
)
468 && compiler
.getSourceManager().isMacroBodyExpansion(fourth
)
469 && (compiler
.getSourceManager().getImmediateMacroCallerLoc(third
)
470 == compiler
.getSourceManager().getImmediateMacroCallerLoc(fourth
))
471 && compiler
.getSourceManager().isAtStartOfImmediateMacroExpansion(third
)
472 && isLastTokenOfImmediateMacroBodyExpansion(fourth
)))
475 third
= fourth
= SourceLocation();
479 auto const range
= compat::getImmediateExpansionRange(
480 compiler
.getSourceManager(), third
);
482 fourth
= range
.second
;
483 assert(third
.isValid());
485 if (third
.isValid() && compiler
.getSourceManager().isMacroBodyExpansion(third
)
486 && compiler
.getSourceManager().isMacroBodyExpansion(fourth
)
487 && (compiler
.getSourceManager().getImmediateMacroCallerLoc(third
)
488 == compiler
.getSourceManager().getImmediateMacroCallerLoc(fourth
)))
490 third
= compiler
.getSourceManager().getSpellingLoc(third
);
491 fourth
= compiler
.getSourceManager().getSpellingLoc(fourth
);
492 assert(third
.isValid());
495 // Ensure that a cast like
499 // (where LONG_MAX expands to __LONG_MAX__, which in turn is a built-in expanding to a value
500 // like 9223372036854775807L) is changed to
504 // instead of trying to add the parentheses to the built-in __LONG_MAX__ definition:
506 if (!(compiler
.getSourceManager().isMacroBodyExpansion(third
)
507 && compiler
.getSourceManager().isMacroBodyExpansion(fourth
)
508 && (compiler
.getSourceManager().getImmediateMacroCallerLoc(third
)
509 == compiler
.getSourceManager().getImmediateMacroCallerLoc(fourth
))
510 && compiler
.getSourceManager().isAtStartOfImmediateMacroExpansion(third
)))
511 // TODO: check that fourth is at end of immediate macro expansion (but
512 // SourceManager::isAtEndOfImmediateMacroExpansion wants a location pointing at the
517 auto const range
= compat::getImmediateExpansionRange(
518 compiler
.getSourceManager(), third
);
520 fourth
= range
.second
;
522 // ...and additionally asymmetrically unwind macros only at the start or end, for code like
524 // (long)ubidi_getVisualIndex(...)
526 // (in editeng/source/editeng/impedit2.cxx) where ubidi_getVisualIndex is an object-like
529 // #define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
531 // (in hwpfilter/source/lexer.cxx):
532 if (!fourth
.isMacroID()) {
533 while (compiler
.getSourceManager().isMacroBodyExpansion(third
)
534 && compiler
.getSourceManager().isAtStartOfImmediateMacroExpansion(third
, &third
))
536 } else if (compiler
.getSourceManager().isMacroBodyExpansion(fourth
)) {
537 while (compiler
.getSourceManager().isMacroArgExpansion(third
)
538 && compiler
.getSourceManager().isAtStartOfImmediateMacroExpansion(third
, &third
)) {}
540 if (!third
.isMacroID()) {
541 while (compiler
.getSourceManager().isMacroBodyExpansion(fourth
)
542 && isLastTokenOfImmediateMacroBodyExpansion(fourth
, &fourth
))
544 } else if (compiler
.getSourceManager().isMacroBodyExpansion(third
)) {
545 while (compiler
.getSourceManager().isMacroArgExpansion(fourth
, &fourth
)) {}
547 if (compiler
.getSourceManager().isMacroBodyExpansion(third
)
548 && compiler
.getSourceManager().isMacroBodyExpansion(fourth
)
549 && (compiler
.getSourceManager().getImmediateMacroCallerLoc(third
)
550 == compiler
.getSourceManager().getImmediateMacroCallerLoc(fourth
)))
552 third
= compiler
.getSourceManager().getSpellingLoc(third
);
553 fourth
= compiler
.getSourceManager().getSpellingLoc(fourth
);
555 assert(third
.isValid());
557 if (firstBegin
.isMacroID() || secondBegin
.isMacroID() || (third
.isValid() && third
.isMacroID())
558 || (fourth
.isValid() && fourth
.isMacroID()))
562 DiagnosticsEngine::Fatal
,
563 "TODO: cannot rewrite C-style cast in macro, needs investigation",
565 << expr
->getSourceRange();
569 unsigned firstLen
= Lexer::MeasureTokenLength(
570 firstBegin
, compiler
.getSourceManager(), compiler
.getLangOpts());
571 for (auto l
= firstBegin
.getLocWithOffset(std::max
<unsigned>(firstLen
, 1));;
572 l
= l
.getLocWithOffset(1))
574 unsigned n
= Lexer::MeasureTokenLength(
575 l
, compiler
.getSourceManager(), compiler
.getLangOpts());
581 unsigned secondLen
= Lexer::MeasureTokenLength(
582 secondBegin
, compiler
.getSourceManager(), compiler
.getLangOpts());
583 for (auto l
= secondBegin
.getLocWithOffset(std::max
<unsigned>(secondLen
, 1));;
584 l
= l
.getLocWithOffset(1))
586 unsigned n
= Lexer::MeasureTokenLength(
587 l
, compiler
.getSourceManager(), compiler
.getLangOpts());
594 auto l
= secondBegin
.getLocWithOffset(-1);
595 auto const c
= compiler
.getSourceManager().getCharacterData(l
)[0];
597 if (compiler
.getSourceManager().getCharacterData(l
.getLocWithOffset(-1))[0] == '\\') {
600 } else if (!(c
== ' ' || c
== '\t' || c
== '\v' || c
== '\f')) {
606 if (rewritten_
.insert(firstBegin
).second
) {
607 if (!replaceText(firstBegin
, firstLen
, functional
? "" : "static_cast<")) {
610 DiagnosticsEngine::Fatal
, "TODO: cannot rewrite #1, needs investigation",
613 DiagnosticsEngine::Note
, "when rewriting this C-style cast", expr
->getExprLoc())
614 << expr
->getSourceRange();
618 if (!replaceText(secondBegin
, secondLen
, functional
? "" : ">")) {
622 DiagnosticsEngine::Fatal
, "TODO: cannot rewrite #2, needs investigation",
625 DiagnosticsEngine::Note
, "when rewriting this C-style cast", expr
->getExprLoc())
626 << expr
->getSourceRange();
631 if (third
.isValid()) {
632 if (rewritten_
.insert(third
).second
) {
633 if (!insertTextBefore(third
, "(")) {
637 DiagnosticsEngine::Fatal
, "TODO: cannot rewrite #3, needs investigation",
640 DiagnosticsEngine::Note
, "when rewriting this C-style cast",
642 << expr
->getSourceRange();
646 if (!insertTextAfterToken(fourth
, ")")) {
650 DiagnosticsEngine::Fatal
, "TODO: cannot rewrite #4, needs investigation",
653 DiagnosticsEngine::Note
, "when rewriting this C-style cast",
655 << expr
->getSourceRange();
664 loplugin::Plugin::Registration
< CStyleCast
> X("cstylecast", true);
668 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */