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