OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 // | |
5 // This implements a Clang tool to rewrite all instances of | |
6 // scoped_refptr<T>'s implicit cast to T (operator T*) to an explicit call to | |
7 // the .get() method. | |
8 | |
9 #include <algorithm> | |
10 #include <memory> | |
11 #include <string> | |
12 | |
13 #include "clang/AST/ASTContext.h" | |
14 #include "clang/ASTMatchers/ASTMatchers.h" | |
15 #include "clang/ASTMatchers/ASTMatchersMacros.h" | |
16 #include "clang/ASTMatchers/ASTMatchFinder.h" | |
17 #include "clang/Basic/SourceManager.h" | |
18 #include "clang/Frontend/FrontendActions.h" | |
19 #include "clang/Lex/Lexer.h" | |
20 #include "clang/Tooling/CommonOptionsParser.h" | |
21 #include "clang/Tooling/Refactoring.h" | |
22 #include "clang/Tooling/Tooling.h" | |
23 #include "llvm/Support/CommandLine.h" | |
24 | |
25 using namespace clang::ast_matchers; | |
26 using clang::tooling::CommonOptionsParser; | |
27 using clang::tooling::Replacement; | |
28 using clang::tooling::Replacements; | |
29 using llvm::StringRef; | |
30 | |
31 namespace clang { | |
32 namespace ast_matchers { | |
33 | |
34 const internal::VariadicDynCastAllOfMatcher<Decl, CXXConversionDecl> | |
35 conversionDecl; | |
36 | |
37 AST_MATCHER(QualType, isBoolean) { | |
38 return Node->isBooleanType(); | |
39 } | |
40 | |
41 } // namespace ast_matchers | |
42 } // namespace clang | |
43 | |
44 namespace { | |
45 | |
46 // Returns true if expr needs to be put in parens (eg: when it is an operator | |
47 // syntactically). | |
48 bool NeedsParens(const clang::Expr* expr) { | |
49 if (llvm::dyn_cast<clang::UnaryOperator>(expr) || | |
50 llvm::dyn_cast<clang::BinaryOperator>(expr) || | |
51 llvm::dyn_cast<clang::ConditionalOperator>(expr)) { | |
52 return true; | |
53 } | |
54 if (const clang::CXXOperatorCallExpr* op = | |
mdempsky
2014/08/07 22:25:10
Might be worth adding a comment explaining that th
dcheng
2014/08/07 23:11:21
Done.
| |
55 llvm::dyn_cast<clang::CXXOperatorCallExpr>(expr)) { | |
56 return op->getOperator() != clang::OO_Call && | |
57 op->getOperator() != clang::OO_Subscript; | |
58 } | |
59 return false; | |
60 } | |
61 | |
62 class GetRewriterCallback : public MatchFinder::MatchCallback { | |
63 public: | |
64 explicit GetRewriterCallback(Replacements* replacements) | |
65 : replacements_(replacements) {} | |
66 virtual void run(const MatchFinder::MatchResult& result) override; | |
67 | |
68 private: | |
69 Replacements* const replacements_; | |
70 }; | |
71 | |
72 void GetRewriterCallback::run(const MatchFinder::MatchResult& result) { | |
73 const clang::CXXMemberCallExpr* const implicit_call = | |
74 result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("call"); | |
75 const clang::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); | |
76 | |
77 if (!implicit_call || !arg) | |
78 return; | |
79 | |
80 clang::CharSourceRange range = clang::CharSourceRange::getTokenRange( | |
81 result.SourceManager->getSpellingLoc(arg->getLocStart()), | |
82 result.SourceManager->getSpellingLoc(arg->getLocEnd())); | |
83 if (!range.isValid()) | |
84 return; // TODO(rsleevi): Log an error? | |
85 | |
86 // Handle cases where an implicit cast is being done by dereferencing a | |
87 // pointer to a scoped_refptr<> (sadly, it happens...) | |
88 // | |
89 // This rewrites both "*foo" and "*(foo)" as "foo->get()". | |
90 if (const clang::UnaryOperator* op = | |
91 llvm::dyn_cast<clang::UnaryOperator>(arg)) { | |
92 if (op->getOpcode() == clang::UO_Deref) { | |
93 const clang::Expr* const sub_expr = | |
94 op->getSubExpr()->IgnoreParenImpCasts(); | |
95 clang::CharSourceRange sub_expr_range = | |
96 clang::CharSourceRange::getTokenRange( | |
97 result.SourceManager->getSpellingLoc(sub_expr->getLocStart()), | |
98 result.SourceManager->getSpellingLoc(sub_expr->getLocEnd())); | |
99 if (!sub_expr_range.isValid()) | |
100 return; // TODO(rsleevi): Log an error? | |
101 std::string inner_text = clang::Lexer::getSourceText( | |
102 sub_expr_range, *result.SourceManager, result.Context->getLangOpts()); | |
103 if (inner_text.empty()) | |
104 return; // TODO(rsleevi): Log an error? | |
105 | |
106 if (NeedsParens(sub_expr)) { | |
107 inner_text.insert(0, "("); | |
108 inner_text.append(")"); | |
109 } | |
110 inner_text.append("->get()"); | |
111 replacements_->insert( | |
112 Replacement(*result.SourceManager, range, inner_text)); | |
113 return; | |
114 } | |
115 } | |
116 | |
117 std::string text = clang::Lexer::getSourceText( | |
118 range, *result.SourceManager, result.Context->getLangOpts()); | |
119 if (text.empty()) | |
120 return; // TODO(rsleevi): Log an error? | |
121 | |
122 // Unwrap any temporaries - for example, custom iterators that return | |
123 // scoped_refptr<T> as part of operator*. Any such iterators should also | |
124 // be declaring a scoped_refptr<T>* operator->, per C++03 24.4.1.1 (Table 72) | |
125 if (const clang::CXXBindTemporaryExpr* op = | |
126 llvm::dyn_cast<clang::CXXBindTemporaryExpr>(arg)) { | |
127 arg = op->getSubExpr(); | |
128 } | |
129 | |
130 // Handle iterators (which are operator* calls, followed by implicit | |
131 // conversions) by rewriting *it as it->get() | |
132 if (const clang::CXXOperatorCallExpr* op = | |
133 llvm::dyn_cast<clang::CXXOperatorCallExpr>(arg)) { | |
134 if (op->getOperator() == clang::OO_Star) { | |
135 text.erase(0, 1); | |
136 text.append("->get()"); | |
mdempsky
2014/08/07 22:25:10
Is it possible we'll NeedParens() here? E.g., I t
dcheng
2014/08/07 23:11:21
Added a comment per our discussion.
| |
137 replacements_->insert(Replacement(*result.SourceManager, range, text)); | |
138 return; | |
139 } | |
140 } | |
141 | |
142 // The only remaining calls should be non-dereferencing calls (eg: member | |
143 // calls), so a simple ".get()" appending should suffice. | |
144 if (NeedsParens(arg)) { | |
145 text.insert(0, "("); | |
146 text.append(")"); | |
147 } | |
148 text.append(".get()"); | |
149 replacements_->insert(Replacement(*result.SourceManager, range, text)); | |
150 } | |
151 | |
152 } // namespace | |
153 | |
154 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); | |
155 | |
156 int main(int argc, const char* argv[]) { | |
157 llvm::cl::OptionCategory category("Remove scoped_refptr conversions"); | |
158 CommonOptionsParser options(argc, argv, category); | |
159 clang::tooling::ClangTool tool(options.getCompilations(), | |
160 options.getSourcePathList()); | |
161 | |
162 MatchFinder match_finder; | |
163 | |
164 // Finds all calls to conversion operator member function. This catches calls | |
165 // to "operator T*", "operator Testable", and "operator bool" equally. | |
166 StatementMatcher overloaded_call_matcher = memberCallExpr( | |
167 thisPointerType(recordDecl(isSameOrDerivedFrom("::scoped_refptr"), | |
168 isTemplateInstantiation())), | |
169 callee(conversionDecl()), | |
170 on(id("arg", expr()))); | |
171 | |
172 // This catches both user-defined conversions (eg: "operator bool") and | |
173 // standard conversion sequence (C++03 13.3.3.1.1), such as converting a | |
174 // pointer to a bool. | |
175 StatementMatcher implicit_to_bool = | |
176 implicitCastExpr(hasImplicitDestinationType(isBoolean())); | |
177 | |
178 // Avoid converting calls to of "operator Testable" -> "bool" and calls of | |
179 // "operator T*" -> "bool". | |
180 StatementMatcher bool_conversion_matcher = hasParent(expr( | |
181 anyOf(expr(implicit_to_bool), expr(hasParent(expr(implicit_to_bool)))))); | |
182 | |
183 // Find all calls to an operator overload that do NOT (ultimately) result in | |
184 // being cast to a bool - eg: where it's being converted to T* and rewrite | |
185 // them to add a call to get(). | |
186 // | |
187 // All bool conversions will be handled with the Testable trick, but that | |
188 // can only be used once "operator T*" is removed, since otherwise it leaves | |
189 // the call ambiguous. | |
190 Replacements get_replacements; | |
191 GetRewriterCallback get_callback(&get_replacements); | |
192 match_finder.addMatcher(id("call", expr(overloaded_call_matcher)), | |
193 &get_callback); | |
194 | |
195 #if 0 | |
196 // Finds all temporary scoped_refptr<T>'s being assigned to a T*. Note that | |
197 // this will result in two callbacks--both the above callback to append get() | |
198 // and this callback will match. | |
199 match_finder.addMatcher( | |
200 id("var", | |
201 varDecl(hasInitializer(ignoringImpCasts( | |
202 id("call", expr(overloaded_call_matcher)))), | |
203 hasType(pointerType()))), | |
204 &callback); | |
205 match_finder.addMatcher( | |
206 binaryOperator( | |
207 hasOperatorName("="), | |
208 hasLHS(declRefExpr(to(id("var", varDecl(hasType(pointerType())))))), | |
209 hasRHS(ignoringParenImpCasts( | |
210 id("call", expr(overloaded_call_matcher))))), | |
211 &callback); | |
212 #endif | |
213 | |
214 std::unique_ptr<clang::tooling::FrontendActionFactory> factory = | |
215 clang::tooling::newFrontendActionFactory(&match_finder); | |
216 int result = tool.run(factory.get()); | |
217 if (result != 0) | |
218 return result; | |
219 | |
220 // Serialization format is documented in tools/clang/scripts/run_tool.py | |
221 llvm::outs() << "==== BEGIN EDITS ====\n"; | |
222 for (const auto& r : get_replacements) { | |
223 std::string replacement_text = r.getReplacementText().str(); | |
224 std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0'); | |
225 llvm::outs() << "r:" << r.getFilePath() << ":" << r.getOffset() << ":" | |
226 << r.getLength() << ":" << replacement_text << "\n"; | |
227 } | |
228 llvm::outs() << "==== END EDITS ====\n"; | |
229 | |
230 return 0; | |
231 } | |
OLD | NEW |