cid#1555694 COPY_INSTEAD_OF_MOVE
[LibreOffice.git] / compilerplugins / clang / unsignedcompare.cxx
blob9b1ed684a90fcae439df321defd0c4d42bb7fec5
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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 #ifndef LO_CLANG_SHARED_PLUGINS
12 // Find explicit casts from signed to unsigned integer in comparison against unsigned integer, where
13 // the cast is presumably used to avoid warnings about signed vs. unsigned comparisons, and could
14 // thus be replaced with o3tl::make_unsigned for clarity.
16 #include <cassert>
18 #include "plugin.hxx"
20 namespace
22 // clang::Type::isSignedIntegerType returns true for more types than what C++ defines as signed
23 // integer types:
24 bool isSignedIntegerType(QualType type)
26 if (auto const t = type->getAs<BuiltinType>())
28 // Assumes that the only extended signed integer type supported by Clang is Int128:
29 switch (t->getKind())
31 case BuiltinType::SChar:
32 case BuiltinType::Short:
33 case BuiltinType::Int:
34 case BuiltinType::Long:
35 case BuiltinType::LongLong:
36 case BuiltinType::Int128:
37 return true;
38 default:
39 break;
42 return false;
45 // clang::Type::isUnsignedIntegerType returns true for more types than what C++ defines as signed
46 // integer types:
47 bool isUnsignedIntegerType(QualType type)
49 if (auto const t = type->getAs<BuiltinType>())
51 // Assumes that the only extended unsigned integer type supported by Clang is UInt128:
52 switch (t->getKind())
54 case BuiltinType::UChar:
55 case BuiltinType::UShort:
56 case BuiltinType::UInt:
57 case BuiltinType::ULong:
58 case BuiltinType::ULongLong:
59 case BuiltinType::UInt128:
60 return true;
61 default:
62 break;
65 return false;
68 int getRank(QualType type)
70 auto const t = type->getAs<BuiltinType>();
71 assert(t != nullptr);
72 // Assumes that the only extended signed/unsigned integer types supported by Clang are Int128
73 // and UInt128:
74 switch (t->getKind())
76 case BuiltinType::SChar:
77 case BuiltinType::UChar:
78 return 0;
79 case BuiltinType::Short:
80 case BuiltinType::UShort:
81 return 1;
82 case BuiltinType::Int:
83 case BuiltinType::UInt:
84 return 2;
85 case BuiltinType::Long:
86 case BuiltinType::ULong:
87 return 3;
88 case BuiltinType::LongLong:
89 case BuiltinType::ULongLong:
90 return 4;
91 case BuiltinType::Int128:
92 case BuiltinType::UInt128:
93 return 5;
94 default:
95 llvm_unreachable("bad integer type");
99 int orderTypes(QualType type1, QualType type2)
101 auto const r1 = getRank(type1);
102 auto const r2 = getRank(type2);
103 return r1 < r2 ? -1 : r1 == r2 ? 0 : 1;
106 class UnsignedCompare : public loplugin::FilteringPlugin<UnsignedCompare>
108 public:
109 explicit UnsignedCompare(loplugin::InstantiationData const& data)
110 : FilteringPlugin(data)
114 bool VisitBinaryOperator(BinaryOperator const* expr)
116 if (ignoreLocation(expr))
118 return true;
120 // o3tl::make_unsigned requires its argument to be non-negative, but this plugin doesn't
121 // check that when it reports its finding, so will produce false positives when the cast is
122 // actually meant to e.g. clamp from a large signed type to a small unsigned type. The
123 // assumption is that this will only be likely the case for BO_EQ (==) and BO_NE (!=)
124 // comparisons, so filter these out here (not sure what case BO_Cmp (<=>) will turn out to
125 // be, so let's keep it here at least for now):
126 switch (expr->getOpcode())
128 case BO_Cmp:
129 case BO_LT:
130 case BO_GT:
131 case BO_LE:
132 case BO_GE:
133 break;
134 default:
135 return true;
137 auto const castL = isCastToUnsigned(expr->getLHS());
138 auto const castR = isCastToUnsigned(expr->getRHS());
139 //TODO(?): Also report somewhat suspicious cases where both sides are cast to unsigned:
140 if ((castL == nullptr) == (castR == nullptr))
142 return true;
144 auto const cast = castL != nullptr ? castL : castR;
145 auto const other = castL != nullptr ? expr->getRHS() : expr->getLHS();
146 auto const otherT = other->IgnoreImpCasts()->getType();
147 if (!isUnsignedIntegerType(otherT))
149 return true;
151 auto const castFromT = cast->getSubExprAsWritten()->getType();
152 auto const castToT = cast->getTypeAsWritten();
153 report(DiagnosticsEngine::Warning,
154 "explicit cast from %0 to %1 (of %select{smaller|equal|larger}2 rank) in comparison "
155 "against %3: if the cast value is known to be non-negative, use o3tl::make_unsigned "
156 "instead of the cast",
157 cast->getExprLoc())
158 << castFromT << castToT << (orderTypes(castToT, castFromT) + 1) << otherT
159 << expr->getSourceRange();
160 return true;
163 bool preRun() override
165 return compiler.getLangOpts().CPlusPlus
166 && compiler.getPreprocessor()
167 .getIdentifierInfo("LIBO_INTERNAL_ONLY")
168 ->hasMacroDefinition();
171 void run() override
173 if (preRun())
175 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
179 private:
180 ExplicitCastExpr const* isCastToUnsigned(Expr const* expr)
182 auto const e = dyn_cast<ExplicitCastExpr>(expr->IgnoreParenImpCasts());
183 if (e == nullptr)
185 return nullptr;
187 auto const t1 = e->getTypeAsWritten();
188 if (!isUnsignedIntegerType(t1))
190 return nullptr;
192 auto const e2 = e->getSubExprAsWritten();
193 auto const t2 = e2->getType();
194 if (!isSignedIntegerType(t2))
196 return nullptr;
198 // Filter out e.g. `size_t(-1)`:
199 if (!e2->isValueDependent())
201 if (auto const val = e2->getIntegerConstantExpr(compiler.getASTContext()))
203 if (val->isNegative())
205 return nullptr;
209 auto loc = e->getBeginLoc();
210 while (compiler.getSourceManager().isMacroArgExpansion(loc))
212 loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
214 // This covers both "plain" code in such include files, as well as expansion of (object-like) macros like
216 // #define SAL_MAX_INT8 ((sal_Int8) 0x7F)
218 // defined in such include files:
219 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(loc)))
220 { //TODO: '#ifdef LIBO_INTERNAL_ONLY' within UNO include files
221 return nullptr;
223 return e;
227 loplugin::Plugin::Registration<UnsignedCompare> unsignedcompare("unsignedcompare");
230 #endif // LO_CLANG_SHARED_PLUGINS
232 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */