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