132 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "SwappedArgumentsCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/Lex/Lexer.h"12#include "clang/Tooling/FixIt.h"13#include "llvm/ADT/SmallPtrSet.h"14 15using namespace clang::ast_matchers;16 17namespace clang::tidy::bugprone {18 19void SwappedArgumentsCheck::registerMatchers(MatchFinder *Finder) {20 Finder->addMatcher(callExpr(unless(isInTemplateInstantiation())).bind("call"),21 this);22}23 24/// Look through lvalue to rvalue and nop casts. This filters out25/// implicit conversions that have no effect on the input but block our view for26/// other implicit casts.27static const Expr *ignoreNoOpCasts(const Expr *E) {28 if (auto *Cast = dyn_cast<CastExpr>(E))29 if (Cast->getCastKind() == CK_LValueToRValue ||30 Cast->getCastKind() == CK_NoOp)31 return ignoreNoOpCasts(Cast->getSubExpr());32 return E;33}34 35/// Restrict the warning to implicit casts that are most likely36/// accidental. User defined or integral conversions fit in this category,37/// lvalue to rvalue or derived to base does not.38static bool isImplicitCastCandidate(const CastExpr *Cast) {39 return Cast->getCastKind() == CK_UserDefinedConversion ||40 Cast->getCastKind() == CK_FloatingToBoolean ||41 Cast->getCastKind() == CK_FloatingToIntegral ||42 Cast->getCastKind() == CK_IntegralToBoolean ||43 Cast->getCastKind() == CK_IntegralToFloating ||44 Cast->getCastKind() == CK_MemberPointerToBoolean ||45 Cast->getCastKind() == CK_PointerToBoolean ||46 (Cast->getCastKind() == CK_IntegralCast &&47 Cast->getSubExpr()->getType()->isBooleanType());48}49 50static bool areTypesSemiEqual(const QualType L, const QualType R) {51 if (L == R)52 return true;53 54 if (!L->isBuiltinType() || !R->isBuiltinType())55 return false;56 57 return (L->isFloatingType() && R->isFloatingType()) ||58 (L->isIntegerType() && R->isIntegerType()) ||59 (L->isBooleanType() && R->isBooleanType());60}61 62static bool areArgumentsPotentiallySwapped(const QualType LTo,63 const QualType RTo,64 const QualType LFrom,65 const QualType RFrom) {66 if (LTo == RTo || LFrom == RFrom)67 return false;68 69 const bool REq = areTypesSemiEqual(RTo, LFrom);70 if (LTo == RFrom && REq)71 return true;72 73 const bool LEq = areTypesSemiEqual(LTo, RFrom);74 if (RTo == LFrom && LEq)75 return true;76 77 if (REq && LEq && !areTypesSemiEqual(RTo, LTo))78 return true;79 80 return false;81}82 83void SwappedArgumentsCheck::check(const MatchFinder::MatchResult &Result) {84 const ASTContext &Ctx = *Result.Context;85 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");86 87 llvm::SmallPtrSet<const Expr *, 4> UsedArgs;88 for (unsigned I = 1, E = Call->getNumArgs(); I < E; ++I) {89 const Expr *LHS = Call->getArg(I - 1);90 const Expr *RHS = Call->getArg(I);91 92 // Only need to check RHS, as LHS has already been covered. We don't want to93 // emit two warnings for a single argument.94 if (UsedArgs.contains(RHS))95 continue;96 97 const auto *LHSCast = dyn_cast<ImplicitCastExpr>(ignoreNoOpCasts(LHS));98 const auto *RHSCast = dyn_cast<ImplicitCastExpr>(ignoreNoOpCasts(RHS));99 100 // Look if this is a potentially swapped argument pair. First look for101 // implicit casts.102 if (!LHSCast || !RHSCast || !isImplicitCastCandidate(LHSCast) ||103 !isImplicitCastCandidate(RHSCast))104 continue;105 106 // If the types that go into the implicit casts match the types of the other107 // argument in the declaration there is a high probability that the108 // arguments were swapped.109 // TODO: We could make use of the edit distance between the argument name110 // and the name of the passed variable in addition to this type based111 // heuristic.112 const Expr *LHSFrom = ignoreNoOpCasts(LHSCast->getSubExpr());113 const Expr *RHSFrom = ignoreNoOpCasts(RHSCast->getSubExpr());114 if (!areArgumentsPotentiallySwapped(LHS->getType(), RHS->getType(),115 LHSFrom->getType(), RHSFrom->getType()))116 continue;117 118 // Emit a warning and fix-its that swap the arguments.119 diag(Call->getBeginLoc(), "argument with implicit conversion from %0 "120 "to %1 followed by argument converted from "121 "%2 to %3, potentially swapped arguments.")122 << LHSFrom->getType() << LHS->getType() << RHSFrom->getType()123 << RHS->getType() << tooling::fixit::createReplacement(*LHS, *RHS, Ctx)124 << tooling::fixit::createReplacement(*RHS, *LHS, Ctx);125 126 // Remember that we emitted a warning for this argument.127 UsedArgs.insert(RHSCast);128 }129}130 131} // namespace clang::tidy::bugprone132