llvm-project
131 строка · 5.0 Кб
1//===--- SwappedArgumentsCheck.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 "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) {
20Finder->addMatcher(callExpr(unless(isInTemplateInstantiation())).bind("call"),
21this);
22}
23
24/// Look through lvalue to rvalue and nop casts. This filters out
25/// implicit conversions that have no effect on the input but block our view for
26/// other implicit casts.
27static const Expr *ignoreNoOpCasts(const Expr *E) {
28if (auto *Cast = dyn_cast<CastExpr>(E))
29if (Cast->getCastKind() == CK_LValueToRValue ||
30Cast->getCastKind() == CK_NoOp)
31return ignoreNoOpCasts(Cast->getSubExpr());
32return E;
33}
34
35/// Restrict the warning to implicit casts that are most likely
36/// 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) {
39return Cast->getCastKind() == CK_UserDefinedConversion ||
40Cast->getCastKind() == CK_FloatingToBoolean ||
41Cast->getCastKind() == CK_FloatingToIntegral ||
42Cast->getCastKind() == CK_IntegralToBoolean ||
43Cast->getCastKind() == CK_IntegralToFloating ||
44Cast->getCastKind() == CK_MemberPointerToBoolean ||
45Cast->getCastKind() == CK_PointerToBoolean ||
46(Cast->getCastKind() == CK_IntegralCast &&
47Cast->getSubExpr()->getType()->isBooleanType());
48}
49
50static bool areTypesSemiEqual(const QualType L, const QualType R) {
51if (L == R)
52return true;
53
54if (!L->isBuiltinType() || !R->isBuiltinType())
55return false;
56
57return (L->isFloatingType() && R->isFloatingType()) ||
58(L->isIntegerType() && R->isIntegerType()) ||
59(L->isBooleanType() && R->isBooleanType());
60}
61
62static bool areArgumentsPotentiallySwapped(const QualType LTo,
63const QualType RTo,
64const QualType LFrom,
65const QualType RFrom) {
66if (LTo == RTo || LFrom == RFrom)
67return false;
68
69const bool REq = areTypesSemiEqual(RTo, LFrom);
70if (LTo == RFrom && REq)
71return true;
72
73bool LEq = areTypesSemiEqual(LTo, RFrom);
74if (RTo == LFrom && LEq)
75return true;
76
77if (REq && LEq && !areTypesSemiEqual(RTo, LTo))
78return true;
79
80return false;
81}
82
83void SwappedArgumentsCheck::check(const MatchFinder::MatchResult &Result) {
84const ASTContext &Ctx = *Result.Context;
85const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
86
87llvm::SmallPtrSet<const Expr *, 4> UsedArgs;
88for (unsigned I = 1, E = Call->getNumArgs(); I < E; ++I) {
89const Expr *LHS = Call->getArg(I - 1);
90const Expr *RHS = Call->getArg(I);
91
92// Only need to check RHS, as LHS has already been covered. We don't want to
93// emit two warnings for a single argument.
94if (UsedArgs.count(RHS))
95continue;
96
97const auto *LHSCast = dyn_cast<ImplicitCastExpr>(ignoreNoOpCasts(LHS));
98const auto *RHSCast = dyn_cast<ImplicitCastExpr>(ignoreNoOpCasts(RHS));
99
100// Look if this is a potentially swapped argument pair. First look for
101// implicit casts.
102if (!LHSCast || !RHSCast || !isImplicitCastCandidate(LHSCast) ||
103!isImplicitCastCandidate(RHSCast))
104continue;
105
106// If the types that go into the implicit casts match the types of the other
107// argument in the declaration there is a high probability that the
108// arguments were swapped.
109// TODO: We could make use of the edit distance between the argument name
110// and the name of the passed variable in addition to this type based
111// heuristic.
112const Expr *LHSFrom = ignoreNoOpCasts(LHSCast->getSubExpr());
113const Expr *RHSFrom = ignoreNoOpCasts(RHSCast->getSubExpr());
114if (!areArgumentsPotentiallySwapped(LHS->getType(), RHS->getType(),
115LHSFrom->getType(), RHSFrom->getType()))
116continue;
117
118// Emit a warning and fix-its that swap the arguments.
119diag(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.
127UsedArgs.insert(RHSCast);
128}
129}
130
131} // namespace clang::tidy::bugprone
132