• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===--- NarrowingConversionsCheck.cpp - clang-tidy------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "NarrowingConversionsCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/Type.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "llvm/ADT/APSInt.h"
14 #include "llvm/ADT/SmallString.h"
15 #include "llvm/ADT/SmallVector.h"
16 
17 #include <cstdint>
18 
19 using namespace clang::ast_matchers;
20 
21 namespace clang {
22 namespace tidy {
23 namespace cppcoreguidelines {
24 
NarrowingConversionsCheck(StringRef Name,ClangTidyContext * Context)25 NarrowingConversionsCheck::NarrowingConversionsCheck(StringRef Name,
26                                                      ClangTidyContext *Context)
27     : ClangTidyCheck(Name, Context),
28       WarnOnFloatingPointNarrowingConversion(
29           Options.get("WarnOnFloatingPointNarrowingConversion", true)),
30       PedanticMode(Options.get("PedanticMode", false)) {}
31 
storeOptions(ClangTidyOptions::OptionMap & Opts)32 void NarrowingConversionsCheck::storeOptions(
33     ClangTidyOptions::OptionMap &Opts) {
34   Options.store(Opts, "WarnOnFloatingPointNarrowingConversion",
35                 WarnOnFloatingPointNarrowingConversion);
36   Options.store(Opts, "PedanticMode", PedanticMode);
37 }
38 
registerMatchers(MatchFinder * Finder)39 void NarrowingConversionsCheck::registerMatchers(MatchFinder *Finder) {
40   // ceil() and floor() are guaranteed to return integers, even though the type
41   // is not integral.
42   const auto IsCeilFloorCallExpr = expr(callExpr(callee(functionDecl(
43       hasAnyName("::ceil", "::std::ceil", "::floor", "::std::floor")))));
44 
45   // Casts:
46   //   i = 0.5;
47   //   void f(int); f(0.5);
48   Finder->addMatcher(
49       traverse(
50           ast_type_traits::TK_AsIs,
51           implicitCastExpr(hasImplicitDestinationType(
52                                hasUnqualifiedDesugaredType(builtinType())),
53                            hasSourceExpression(hasType(
54                                hasUnqualifiedDesugaredType(builtinType()))),
55                            unless(hasSourceExpression(IsCeilFloorCallExpr)),
56                            unless(hasParent(castExpr())),
57                            unless(isInTemplateInstantiation()))
58               .bind("cast")),
59       this);
60 
61   // Binary operators:
62   //   i += 0.5;
63   Finder->addMatcher(
64       binaryOperator(
65           isAssignmentOperator(),
66           hasLHS(expr(hasType(hasUnqualifiedDesugaredType(builtinType())))),
67           hasRHS(expr(hasType(hasUnqualifiedDesugaredType(builtinType())))),
68           unless(hasRHS(IsCeilFloorCallExpr)),
69           unless(isInTemplateInstantiation()),
70           // The `=` case generates an implicit cast
71           // which is covered by the previous matcher.
72           unless(hasOperatorName("=")))
73           .bind("binary_op"),
74       this);
75 }
76 
getBuiltinType(const Expr & E)77 static const BuiltinType *getBuiltinType(const Expr &E) {
78   return E.getType().getCanonicalType().getTypePtr()->getAs<BuiltinType>();
79 }
80 
getUnqualifiedType(const Expr & E)81 static QualType getUnqualifiedType(const Expr &E) {
82   return E.getType().getUnqualifiedType();
83 }
84 
getConstantExprValue(const ASTContext & Ctx,const Expr & E)85 static APValue getConstantExprValue(const ASTContext &Ctx, const Expr &E) {
86   if (auto IntegerConstant = E.getIntegerConstantExpr(Ctx))
87     return APValue(*IntegerConstant);
88   APValue Constant;
89   if (Ctx.getLangOpts().CPlusPlus && E.isCXX11ConstantExpr(Ctx, &Constant))
90     return Constant;
91   return {};
92 }
93 
getIntegerConstantExprValue(const ASTContext & Context,const Expr & E,llvm::APSInt & Value)94 static bool getIntegerConstantExprValue(const ASTContext &Context,
95                                         const Expr &E, llvm::APSInt &Value) {
96   APValue Constant = getConstantExprValue(Context, E);
97   if (!Constant.isInt())
98     return false;
99   Value = Constant.getInt();
100   return true;
101 }
102 
getFloatingConstantExprValue(const ASTContext & Context,const Expr & E,llvm::APFloat & Value)103 static bool getFloatingConstantExprValue(const ASTContext &Context,
104                                          const Expr &E, llvm::APFloat &Value) {
105   APValue Constant = getConstantExprValue(Context, E);
106   if (!Constant.isFloat())
107     return false;
108   Value = Constant.getFloat();
109   return true;
110 }
111 
112 namespace {
113 
114 struct IntegerRange {
Containsclang::tidy::cppcoreguidelines::__anon58a210e70111::IntegerRange115   bool Contains(const IntegerRange &From) const {
116     return llvm::APSInt::compareValues(Lower, From.Lower) <= 0 &&
117            llvm::APSInt::compareValues(Upper, From.Upper) >= 0;
118   }
119 
Containsclang::tidy::cppcoreguidelines::__anon58a210e70111::IntegerRange120   bool Contains(const llvm::APSInt &Value) const {
121     return llvm::APSInt::compareValues(Lower, Value) <= 0 &&
122            llvm::APSInt::compareValues(Upper, Value) >= 0;
123   }
124 
125   llvm::APSInt Lower;
126   llvm::APSInt Upper;
127 };
128 
129 } // namespace
130 
createFromType(const ASTContext & Context,const BuiltinType & T)131 static IntegerRange createFromType(const ASTContext &Context,
132                                    const BuiltinType &T) {
133   if (T.isFloatingPoint()) {
134     unsigned PrecisionBits = llvm::APFloatBase::semanticsPrecision(
135         Context.getFloatTypeSemantics(T.desugar()));
136     // Contrary to two's complement integer, floating point values are
137     // symmetric and have the same number of positive and negative values.
138     // The range of valid integers for a floating point value is:
139     // [-2^PrecisionBits, 2^PrecisionBits]
140 
141     // Values are created with PrecisionBits plus two bits:
142     // - One to express the missing negative value of 2's complement
143     //   representation.
144     // - One for the sign.
145     llvm::APSInt UpperValue(PrecisionBits + 2, /*isUnsigned*/ false);
146     UpperValue.setBit(PrecisionBits);
147     llvm::APSInt LowerValue(PrecisionBits + 2, /*isUnsigned*/ false);
148     LowerValue.setBit(PrecisionBits);
149     LowerValue.setSignBit();
150     return {LowerValue, UpperValue};
151   }
152   assert(T.isInteger() && "Unexpected builtin type");
153   uint64_t TypeSize = Context.getTypeSize(&T);
154   bool IsUnsignedInteger = T.isUnsignedInteger();
155   return {llvm::APSInt::getMinValue(TypeSize, IsUnsignedInteger),
156           llvm::APSInt::getMaxValue(TypeSize, IsUnsignedInteger)};
157 }
158 
isWideEnoughToHold(const ASTContext & Context,const BuiltinType & FromType,const BuiltinType & ToType)159 static bool isWideEnoughToHold(const ASTContext &Context,
160                                const BuiltinType &FromType,
161                                const BuiltinType &ToType) {
162   IntegerRange FromIntegerRange = createFromType(Context, FromType);
163   IntegerRange ToIntegerRange = createFromType(Context, ToType);
164   return ToIntegerRange.Contains(FromIntegerRange);
165 }
166 
isWideEnoughToHold(const ASTContext & Context,const llvm::APSInt & IntegerConstant,const BuiltinType & ToType)167 static bool isWideEnoughToHold(const ASTContext &Context,
168                                const llvm::APSInt &IntegerConstant,
169                                const BuiltinType &ToType) {
170   IntegerRange ToIntegerRange = createFromType(Context, ToType);
171   return ToIntegerRange.Contains(IntegerConstant);
172 }
173 
getValueAsString(const llvm::APSInt & Value,uint64_t HexBits)174 static llvm::SmallString<64> getValueAsString(const llvm::APSInt &Value,
175                                               uint64_t HexBits) {
176   llvm::SmallString<64> Str;
177   Value.toString(Str, 10);
178   if (HexBits > 0) {
179     Str.append(" (0x");
180     llvm::SmallString<32> HexValue;
181     Value.toStringUnsigned(HexValue, 16);
182     for (size_t I = HexValue.size(); I < (HexBits / 4); ++I)
183       Str.append("0");
184     Str.append(HexValue);
185     Str.append(")");
186   }
187   return Str;
188 }
189 
diagNarrowType(SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)190 void NarrowingConversionsCheck::diagNarrowType(SourceLocation SourceLoc,
191                                                const Expr &Lhs,
192                                                const Expr &Rhs) {
193   diag(SourceLoc, "narrowing conversion from %0 to %1")
194       << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs);
195 }
196 
diagNarrowTypeToSignedInt(SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)197 void NarrowingConversionsCheck::diagNarrowTypeToSignedInt(
198     SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs) {
199   diag(SourceLoc, "narrowing conversion from %0 to signed type %1 is "
200                   "implementation-defined")
201       << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs);
202 }
203 
diagNarrowIntegerConstant(SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs,const llvm::APSInt & Value)204 void NarrowingConversionsCheck::diagNarrowIntegerConstant(
205     SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs,
206     const llvm::APSInt &Value) {
207   diag(SourceLoc,
208        "narrowing conversion from constant value %0 of type %1 to %2")
209       << getValueAsString(Value, /*NoHex*/ 0) << getUnqualifiedType(Rhs)
210       << getUnqualifiedType(Lhs);
211 }
212 
diagNarrowIntegerConstantToSignedInt(SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs,const llvm::APSInt & Value,const uint64_t HexBits)213 void NarrowingConversionsCheck::diagNarrowIntegerConstantToSignedInt(
214     SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs,
215     const llvm::APSInt &Value, const uint64_t HexBits) {
216   diag(SourceLoc, "narrowing conversion from constant value %0 of type %1 "
217                   "to signed type %2 is implementation-defined")
218       << getValueAsString(Value, HexBits) << getUnqualifiedType(Rhs)
219       << getUnqualifiedType(Lhs);
220 }
221 
diagNarrowConstant(SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)222 void NarrowingConversionsCheck::diagNarrowConstant(SourceLocation SourceLoc,
223                                                    const Expr &Lhs,
224                                                    const Expr &Rhs) {
225   diag(SourceLoc, "narrowing conversion from constant %0 to %1")
226       << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs);
227 }
228 
diagConstantCast(SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)229 void NarrowingConversionsCheck::diagConstantCast(SourceLocation SourceLoc,
230                                                  const Expr &Lhs,
231                                                  const Expr &Rhs) {
232   diag(SourceLoc, "constant value should be of type of type %0 instead of %1")
233       << getUnqualifiedType(Lhs) << getUnqualifiedType(Rhs);
234 }
235 
diagNarrowTypeOrConstant(const ASTContext & Context,SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)236 void NarrowingConversionsCheck::diagNarrowTypeOrConstant(
237     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
238     const Expr &Rhs) {
239   APValue Constant = getConstantExprValue(Context, Rhs);
240   if (Constant.isInt())
241     return diagNarrowIntegerConstant(SourceLoc, Lhs, Rhs, Constant.getInt());
242   if (Constant.isFloat())
243     return diagNarrowConstant(SourceLoc, Lhs, Rhs);
244   return diagNarrowType(SourceLoc, Lhs, Rhs);
245 }
246 
handleIntegralCast(const ASTContext & Context,SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)247 void NarrowingConversionsCheck::handleIntegralCast(const ASTContext &Context,
248                                                    SourceLocation SourceLoc,
249                                                    const Expr &Lhs,
250                                                    const Expr &Rhs) {
251   const BuiltinType *ToType = getBuiltinType(Lhs);
252   // From [conv.integral]p7.3.8:
253   // Conversions to unsigned integer is well defined so no warning is issued.
254   // "The resulting value is the smallest unsigned value equal to the source
255   // value modulo 2^n where n is the number of bits used to represent the
256   // destination type."
257   if (ToType->isUnsignedInteger())
258     return;
259   const BuiltinType *FromType = getBuiltinType(Rhs);
260   llvm::APSInt IntegerConstant;
261   if (getIntegerConstantExprValue(Context, Rhs, IntegerConstant)) {
262     if (!isWideEnoughToHold(Context, IntegerConstant, *ToType))
263       diagNarrowIntegerConstantToSignedInt(SourceLoc, Lhs, Rhs, IntegerConstant,
264                                            Context.getTypeSize(FromType));
265     return;
266   }
267   if (!isWideEnoughToHold(Context, *FromType, *ToType))
268     diagNarrowTypeToSignedInt(SourceLoc, Lhs, Rhs);
269 }
270 
handleIntegralToBoolean(const ASTContext & Context,SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)271 void NarrowingConversionsCheck::handleIntegralToBoolean(
272     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
273     const Expr &Rhs) {
274   // Conversion from Integral to Bool value is well defined.
275 
276   // We keep this function (even if it is empty) to make sure that
277   // handleImplicitCast and handleBinaryOperator are symmetric in their behavior
278   // and handle the same cases.
279 }
280 
handleIntegralToFloating(const ASTContext & Context,SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)281 void NarrowingConversionsCheck::handleIntegralToFloating(
282     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
283     const Expr &Rhs) {
284   const BuiltinType *ToType = getBuiltinType(Lhs);
285   llvm::APSInt IntegerConstant;
286   if (getIntegerConstantExprValue(Context, Rhs, IntegerConstant)) {
287     if (!isWideEnoughToHold(Context, IntegerConstant, *ToType))
288       diagNarrowIntegerConstant(SourceLoc, Lhs, Rhs, IntegerConstant);
289     return;
290   }
291   const BuiltinType *FromType = getBuiltinType(Rhs);
292   if (!isWideEnoughToHold(Context, *FromType, *ToType))
293     diagNarrowType(SourceLoc, Lhs, Rhs);
294 }
295 
handleFloatingToIntegral(const ASTContext & Context,SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)296 void NarrowingConversionsCheck::handleFloatingToIntegral(
297     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
298     const Expr &Rhs) {
299   llvm::APFloat FloatConstant(0.0);
300 
301   // We always warn when Rhs is non-constexpr.
302   if (!getFloatingConstantExprValue(Context, Rhs, FloatConstant))
303     return diagNarrowType(SourceLoc, Lhs, Rhs);
304 
305   QualType DestType = Lhs.getType();
306   unsigned DestWidth = Context.getIntWidth(DestType);
307   bool DestSigned = DestType->isSignedIntegerOrEnumerationType();
308   llvm::APSInt Result = llvm::APSInt(DestWidth, !DestSigned);
309   bool IsExact = false;
310   bool Overflows = FloatConstant.convertToInteger(
311                        Result, llvm::APFloat::rmTowardZero, &IsExact) &
312                    llvm::APFloat::opInvalidOp;
313   // We warn iff the constant floating point value is not exactly representable.
314   if (Overflows || !IsExact)
315     return diagNarrowConstant(SourceLoc, Lhs, Rhs);
316 
317   if (PedanticMode)
318     return diagConstantCast(SourceLoc, Lhs, Rhs);
319 }
320 
handleFloatingToBoolean(const ASTContext & Context,SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)321 void NarrowingConversionsCheck::handleFloatingToBoolean(
322     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
323     const Expr &Rhs) {
324   return diagNarrowTypeOrConstant(Context, SourceLoc, Lhs, Rhs);
325 }
326 
handleBooleanToSignedIntegral(const ASTContext & Context,SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)327 void NarrowingConversionsCheck::handleBooleanToSignedIntegral(
328     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
329     const Expr &Rhs) {
330   // Conversion from Bool to SignedIntegral value is well defined.
331 
332   // We keep this function (even if it is empty) to make sure that
333   // handleImplicitCast and handleBinaryOperator are symmetric in their behavior
334   // and handle the same cases.
335 }
336 
handleFloatingCast(const ASTContext & Context,SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)337 void NarrowingConversionsCheck::handleFloatingCast(const ASTContext &Context,
338                                                    SourceLocation SourceLoc,
339                                                    const Expr &Lhs,
340                                                    const Expr &Rhs) {
341   if (WarnOnFloatingPointNarrowingConversion) {
342     const BuiltinType *ToType = getBuiltinType(Lhs);
343     APValue Constant = getConstantExprValue(Context, Rhs);
344     if (Constant.isFloat()) {
345       // From [dcl.init.list]p7.2:
346       // Floating point constant narrowing only takes place when the value is
347       // not within destination range. We convert the value to the destination
348       // type and check if the resulting value is infinity.
349       llvm::APFloat Tmp = Constant.getFloat();
350       bool UnusedLosesInfo;
351       Tmp.convert(Context.getFloatTypeSemantics(ToType->desugar()),
352                   llvm::APFloatBase::rmNearestTiesToEven, &UnusedLosesInfo);
353       if (Tmp.isInfinity())
354         diagNarrowConstant(SourceLoc, Lhs, Rhs);
355       return;
356     }
357     const BuiltinType *FromType = getBuiltinType(Rhs);
358     if (ToType->getKind() < FromType->getKind())
359       diagNarrowType(SourceLoc, Lhs, Rhs);
360   }
361 }
362 
handleBinaryOperator(const ASTContext & Context,SourceLocation SourceLoc,const Expr & Lhs,const Expr & Rhs)363 void NarrowingConversionsCheck::handleBinaryOperator(const ASTContext &Context,
364                                                      SourceLocation SourceLoc,
365                                                      const Expr &Lhs,
366                                                      const Expr &Rhs) {
367   assert(!Lhs.isInstantiationDependent() && !Rhs.isInstantiationDependent() &&
368          "Dependent types must be check before calling this function");
369   const BuiltinType *LhsType = getBuiltinType(Lhs);
370   const BuiltinType *RhsType = getBuiltinType(Rhs);
371   if (RhsType == nullptr || LhsType == nullptr)
372     return;
373   if (RhsType->getKind() == BuiltinType::Bool && LhsType->isSignedInteger())
374     return handleBooleanToSignedIntegral(Context, SourceLoc, Lhs, Rhs);
375   if (RhsType->isInteger() && LhsType->getKind() == BuiltinType::Bool)
376     return handleIntegralToBoolean(Context, SourceLoc, Lhs, Rhs);
377   if (RhsType->isInteger() && LhsType->isFloatingPoint())
378     return handleIntegralToFloating(Context, SourceLoc, Lhs, Rhs);
379   if (RhsType->isInteger() && LhsType->isInteger())
380     return handleIntegralCast(Context, SourceLoc, Lhs, Rhs);
381   if (RhsType->isFloatingPoint() && LhsType->getKind() == BuiltinType::Bool)
382     return handleFloatingToBoolean(Context, SourceLoc, Lhs, Rhs);
383   if (RhsType->isFloatingPoint() && LhsType->isInteger())
384     return handleFloatingToIntegral(Context, SourceLoc, Lhs, Rhs);
385   if (RhsType->isFloatingPoint() && LhsType->isFloatingPoint())
386     return handleFloatingCast(Context, SourceLoc, Lhs, Rhs);
387 }
388 
handleConditionalOperator(const ASTContext & Context,const Expr & Lhs,const Expr & Rhs)389 bool NarrowingConversionsCheck::handleConditionalOperator(
390     const ASTContext &Context, const Expr &Lhs, const Expr &Rhs) {
391   if (const auto *CO = llvm::dyn_cast<ConditionalOperator>(&Rhs)) {
392     // We have an expression like so: `output = cond ? lhs : rhs`
393     // From the point of view of narrowing conversion we treat it as two
394     // expressions `output = lhs` and `output = rhs`.
395     handleBinaryOperator(Context, CO->getLHS()->getExprLoc(), Lhs,
396                          *CO->getLHS());
397     handleBinaryOperator(Context, CO->getRHS()->getExprLoc(), Lhs,
398                          *CO->getRHS());
399     return true;
400   }
401   return false;
402 }
403 
handleImplicitCast(const ASTContext & Context,const ImplicitCastExpr & Cast)404 void NarrowingConversionsCheck::handleImplicitCast(
405     const ASTContext &Context, const ImplicitCastExpr &Cast) {
406   if (Cast.getExprLoc().isMacroID())
407     return;
408   const Expr &Lhs = Cast;
409   const Expr &Rhs = *Cast.getSubExpr();
410   if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent())
411     return;
412   if (handleConditionalOperator(Context, Lhs, Rhs))
413     return;
414   SourceLocation SourceLoc = Lhs.getExprLoc();
415   switch (Cast.getCastKind()) {
416   case CK_BooleanToSignedIntegral:
417     return handleBooleanToSignedIntegral(Context, SourceLoc, Lhs, Rhs);
418   case CK_IntegralToBoolean:
419     return handleIntegralToBoolean(Context, SourceLoc, Lhs, Rhs);
420   case CK_IntegralToFloating:
421     return handleIntegralToFloating(Context, SourceLoc, Lhs, Rhs);
422   case CK_IntegralCast:
423     return handleIntegralCast(Context, SourceLoc, Lhs, Rhs);
424   case CK_FloatingToBoolean:
425     return handleFloatingToBoolean(Context, SourceLoc, Lhs, Rhs);
426   case CK_FloatingToIntegral:
427     return handleFloatingToIntegral(Context, SourceLoc, Lhs, Rhs);
428   case CK_FloatingCast:
429     return handleFloatingCast(Context, SourceLoc, Lhs, Rhs);
430   default:
431     break;
432   }
433 }
434 
handleBinaryOperator(const ASTContext & Context,const BinaryOperator & Op)435 void NarrowingConversionsCheck::handleBinaryOperator(const ASTContext &Context,
436                                                      const BinaryOperator &Op) {
437   if (Op.getBeginLoc().isMacroID())
438     return;
439   const Expr &Lhs = *Op.getLHS();
440   const Expr &Rhs = *Op.getRHS();
441   if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent())
442     return;
443   if (handleConditionalOperator(Context, Lhs, Rhs))
444     return;
445   handleBinaryOperator(Context, Rhs.getBeginLoc(), Lhs, Rhs);
446 }
447 
check(const MatchFinder::MatchResult & Result)448 void NarrowingConversionsCheck::check(const MatchFinder::MatchResult &Result) {
449   if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("binary_op"))
450     return handleBinaryOperator(*Result.Context, *Op);
451   if (const auto *Cast = Result.Nodes.getNodeAs<ImplicitCastExpr>("cast"))
452     return handleImplicitCast(*Result.Context, *Cast);
453   llvm_unreachable("must be binary operator or cast expression");
454 }
455 } // namespace cppcoreguidelines
456 } // namespace tidy
457 } // namespace clang
458