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