1 #include "UseNamedPointConstantsCheck.h"
2
3 #include <cassert>
4 #include <clang/AST/Decl.h>
5 #include <clang/AST/DeclCXX.h>
6 #include <clang/AST/Expr.h>
7 #include <clang/AST/ExprCXX.h>
8 #include <clang/ASTMatchers/ASTMatchFinder.h>
9 #include <clang/ASTMatchers/ASTMatchers.h>
10 #include <clang/ASTMatchers/ASTMatchersInternal.h>
11 #include <clang/Basic/Diagnostic.h>
12 #include <clang/Basic/LLVM.h>
13 #include <clang/Basic/SourceLocation.h>
14 #include <clang/Lex/Lexer.h>
15 #include <llvm/ADT/APInt.h>
16 #include <llvm/Support/Casting.h>
17 #include <map>
18 #include <string>
19 #include <tuple>
20 #include <utility>
21
22 #include "Utils.h"
23 #include "clang/AST/OperationKinds.h"
24 #include "../../src/cata_assert.h"
25
26 using namespace clang::ast_matchers;
27
28 namespace clang
29 {
30 namespace tidy
31 {
32 namespace cata
33 {
34
isInteger(const std::string & bind)35 static auto isInteger( const std::string &bind )
36 {
37 return expr(
38 anyOf(
39 integerLiteral(),
40 unaryOperator(
41 anyOf( hasOperatorName( "+" ), hasOperatorName( "-" ) ),
42 hasUnaryOperand( integerLiteral() )
43 )
44 )
45 ).bind( bind );
46 }
47
registerMatchers(MatchFinder * Finder)48 void UseNamedPointConstantsCheck::registerMatchers( MatchFinder *Finder )
49 {
50 Finder->addMatcher(
51 cxxConstructExpr(
52 hasDeclaration( isPointConstructor().bind( "constructorDecl" ) ),
53 testWhetherConstructingTemporary(),
54 testWhetherParentIsVarDecl(),
55 argumentCountIs( 2 ),
56 hasArgument( 0, isInteger( "xexpr" ) ),
57 hasArgument( 1, isInteger( "yexpr" ) )
58 ).bind( "constructorCall" ),
59 this
60 );
61 Finder->addMatcher(
62 cxxConstructExpr(
63 hasDeclaration( isPointConstructor().bind( "constructorDecl" ) ),
64 testWhetherConstructingTemporary(),
65 testWhetherParentIsVarDecl(),
66 argumentCountIs( 3 ),
67 hasArgument( 0, isInteger( "xexpr" ) ),
68 hasArgument( 1, isInteger( "yexpr" ) ),
69 hasArgument( 2, isInteger( "zexpr" ) )
70 ).bind( "constructorCall" ),
71 this
72 );
73 }
74
75 static const std::map<std::pair<int, int>, std::string> PointConstants = {
76 { { 0, 0 }, "point_zero" },
77 { { 0, -1 }, "point_north" },
78 { { 1, -1 }, "point_north_east" },
79 { { 1, 0 }, "point_east" },
80 { { 1, 1 }, "point_south_east" },
81 { { 0, 1 }, "point_south" },
82 { { -1, 1 }, "point_south_west" },
83 { { -1, 0 }, "point_west" },
84 { { -1, -1 }, "point_north_west" },
85 };
86
87 static const std::map<std::tuple<int, int, int>, std::string> TripointConstants = {
88 { std::make_tuple( 0, 0, 0 ), "tripoint_zero" },
89 { std::make_tuple( 0, 0, 1 ), "tripoint_above" },
90 { std::make_tuple( 0, 0, -1 ), "tripoint_below" },
91 { std::make_tuple( 0, -1, 0 ), "tripoint_north" },
92 { std::make_tuple( 1, -1, 0 ), "tripoint_north_east" },
93 { std::make_tuple( 1, 0, 0 ), "tripoint_east" },
94 { std::make_tuple( 1, 1, 0 ), "tripoint_south_east" },
95 { std::make_tuple( 0, 1, 0 ), "tripoint_south" },
96 { std::make_tuple( -1, 1, 0 ), "tripoint_south_west" },
97 { std::make_tuple( -1, 0, 0 ), "tripoint_west" },
98 { std::make_tuple( -1, -1, 0 ), "tripoint_north_west" },
99 };
100
CheckConstructor(UseNamedPointConstantsCheck & Check,const MatchFinder::MatchResult & Result)101 static void CheckConstructor( UseNamedPointConstantsCheck &Check,
102 const MatchFinder::MatchResult &Result )
103 {
104 const CXXConstructExpr *ConstructorCall =
105 Result.Nodes.getNodeAs<CXXConstructExpr>( "constructorCall" );
106 const CXXConstructorDecl *ConstructorDecl =
107 Result.Nodes.getNodeAs<CXXConstructorDecl>( "constructorDecl" );
108 const Expr *XExpr = Result.Nodes.getNodeAs<Expr>( "xexpr" );
109 const Expr *YExpr = Result.Nodes.getNodeAs<Expr>( "yexpr" );
110 const Expr *ZExpr = Result.Nodes.getNodeAs<Expr>( "zexpr" );
111 const Expr *TempParent = Result.Nodes.getNodeAs<Expr>( "temp" );
112 const VarDecl *VarDeclParent = Result.Nodes.getNodeAs<VarDecl>( "parentVarDecl" );
113 if( !ConstructorCall || !ConstructorDecl || !XExpr || !YExpr ) {
114 return;
115 }
116
117 std::map<std::string, int> Args;
118
119 auto insertArgIf = [&]( const Expr * E, const char *Key ) {
120 int Value;
121 if( E == nullptr ) {
122 return;
123 } else if( const IntegerLiteral *Literal = dyn_cast<IntegerLiteral>( E ) ) {
124 Value = Literal->getValue().getZExtValue();
125 } else if( const UnaryOperator *UOp = dyn_cast<UnaryOperator>( E ) ) {
126 const IntegerLiteral *Literal = dyn_cast<IntegerLiteral>( UOp->getSubExpr() );
127 Value = Literal->getValue().getZExtValue();
128 if( UOp->getOpcode() == UO_Minus ) {
129 Value = -Value;
130 }
131 } else {
132 cata_assert( false ); // NOLINT(misc-static-assert,cert-dcl03-c)
133 }
134 Args.insert( { Key, Value } );
135 };
136 insertArgIf( XExpr, "x" );
137 insertArgIf( YExpr, "y" );
138 insertArgIf( ZExpr, "z" );
139
140 std::string Replacement;
141
142 if( ZExpr ) {
143 auto it = TripointConstants.find( std::make_tuple( Args["x"], Args["y"], Args["z"] ) );
144 if( it != TripointConstants.end() ) {
145 Replacement = it->second;
146 }
147 } else {
148 auto it = PointConstants.find( std::make_pair( Args["x"], Args["y"] ) );
149 if( it != PointConstants.end() ) {
150 Replacement = it->second;
151 }
152 }
153
154 if( Replacement.empty() ) {
155 return;
156 }
157
158 // Avoid replacing the definitions of the constants themselves
159 if( VarDeclParent && VarDeclParent->getName() == Replacement ) {
160 return;
161 }
162
163 const std::string UserVisibleReplacement = Replacement;
164
165 const unsigned int LastArg = ConstructorCall->getNumArgs() - 1;
166 SourceRange SourceRangeToReplace( ConstructorCall->getArg( 0 )->getBeginLoc(),
167 ConstructorCall->getArg( LastArg )->getEndLoc() );
168
169 if( TempParent ) {
170 SourceRangeToReplace = ConstructorCall->getSourceRange();
171 // Work around buggy source range for default parameters
172 const std::string ReplacedText = getText( Result, ConstructorCall );
173 if( ReplacedText.size() >= 2 && ReplacedText.substr( 0, 2 ) == "= " ) {
174 Replacement = "= " + Replacement;
175 }
176 }
177
178 CharSourceRange CharRangeToReplace = Lexer::makeFileCharRange(
179 CharSourceRange::getTokenRange( SourceRangeToReplace ), *Result.SourceManager,
180 Check.getLangOpts() );
181
182 Check.diag(
183 ConstructorCall->getBeginLoc(),
184 "Prefer constructing %0 from named constant '%1' rather than explicit integer arguments."
185 ) << ConstructorDecl->getParent() << UserVisibleReplacement <<
186 FixItHint::CreateReplacement( CharRangeToReplace, Replacement );
187 }
188
check(const MatchFinder::MatchResult & Result)189 void UseNamedPointConstantsCheck::check( const MatchFinder::MatchResult &Result )
190 {
191 CheckConstructor( *this, Result );
192 }
193
194 } // namespace cata
195 } // namespace tidy
196 } // namespace clang
197