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 convert all instances of std::string("") to | |
6 // std::string(). The latter is more efficient (as std::string doesn't have to | |
7 // take a copy of an empty string) and generates fewer instructions as well. It | |
8 // should be run using the tools/clang/scripts/run_tool.py helper. | |
9 | |
10 #include "clang/ASTMatchers/ASTMatchers.h" | |
11 #include "clang/ASTMatchers/ASTMatchFinder.h" | |
12 #include "clang/Basic/SourceManager.h" | |
13 #include "clang/Frontend/FrontendActions.h" | |
14 #include "clang/Tooling/CommonOptionsParser.h" | |
15 #include "clang/Tooling/Refactoring.h" | |
16 #include "clang/Tooling/Tooling.h" | |
17 #include "llvm/Support/CommandLine.h" | |
18 | |
19 using clang::ast_matchers::MatchFinder; | |
20 using clang::ast_matchers::argumentCountIs; | |
21 using clang::ast_matchers::bindTemporaryExpr; | |
22 using clang::ast_matchers::constructorDecl; | |
23 using clang::ast_matchers::constructExpr; | |
24 using clang::ast_matchers::defaultArgExpr; | |
25 using clang::ast_matchers::expr; | |
26 using clang::ast_matchers::forEach; | |
27 using clang::ast_matchers::has; | |
28 using clang::ast_matchers::hasArgument; | |
29 using clang::ast_matchers::hasDeclaration; | |
30 using clang::ast_matchers::matchesName; | |
31 using clang::ast_matchers::id; | |
32 using clang::ast_matchers::methodDecl; | |
33 using clang::ast_matchers::newExpr; | |
34 using clang::ast_matchers::ofClass; | |
35 using clang::ast_matchers::unless; | |
36 using clang::ast_matchers::varDecl; | |
37 using clang::tooling::CommonOptionsParser; | |
38 using clang::tooling::Replacement; | |
39 using clang::tooling::Replacements; | |
40 | |
41 namespace { | |
42 | |
43 bool IsNullConstant(const clang::Expr& expr, clang::ASTContext* context) { | |
44 return expr.isNullPointerConstant(*context, | |
45 clang::Expr::NPC_ValueDependentIsNotNull) != | |
46 clang::Expr::NPCK_NotNull; | |
47 } | |
48 | |
49 // Handles replacements for stack and heap-allocated instances, e.g.: | |
50 // scoped_ptr<T> a(NULL); | |
51 // scoped_ptr<T>* b = new scoped_ptr<T>(NULL); | |
52 // ...though the latter should be pretty rare. | |
53 class ConstructorCallback : public MatchFinder::MatchCallback { | |
54 public: | |
55 ConstructorCallback(Replacements* replacements) | |
56 : replacements_(replacements) {} | |
57 | |
58 virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; | |
59 | |
60 private: | |
61 Replacements* const replacements_; | |
62 }; | |
63 | |
64 // Handles replacements for invocations of scoped_ptr<T>(NULL) in an initializer | |
65 // list. | |
66 class InitializerCallback : public MatchFinder::MatchCallback { | |
67 public: | |
68 InitializerCallback(Replacements* replacements) | |
69 : replacements_(replacements) {} | |
70 | |
71 virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; | |
72 | |
73 private: | |
74 Replacements* const replacements_; | |
75 }; | |
76 | |
77 // Handles replacements for invocations of scoped_ptr<T>(NULL) in a temporary | |
78 // context, e.g. return scoped_ptr<T>(NULL). | |
79 class TemporaryCallback : public MatchFinder::MatchCallback { | |
80 public: | |
81 TemporaryCallback(Replacements* replacements) : replacements_(replacements) {} | |
82 | |
83 virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; | |
84 | |
85 private: | |
86 Replacements* const replacements_; | |
87 }; | |
88 | |
89 class EmptyStringConverter { | |
90 public: | |
91 explicit EmptyStringConverter(Replacements* replacements) | |
92 : constructor_callback_(replacements), | |
93 initializer_callback_(replacements), | |
94 temporary_callback_(replacements) {} | |
95 | |
96 void SetupMatchers(MatchFinder* match_finder); | |
97 | |
98 private: | |
99 ConstructorCallback constructor_callback_; | |
100 InitializerCallback initializer_callback_; | |
101 TemporaryCallback temporary_callback_; | |
102 }; | |
103 | |
104 void EmptyStringConverter::SetupMatchers(MatchFinder* match_finder) { | |
105 const char kPattern[] = "^::(scoped_ptr|scoped_ptr_malloc)$"; | |
106 const clang::ast_matchers::StatementMatcher& constructor_call = id( | |
107 "call", | |
108 constructExpr(hasDeclaration(methodDecl(ofClass(matchesName(kPattern)))), | |
109 argumentCountIs(1), | |
110 hasArgument(0, id("arg", expr())), | |
111 unless(hasArgument(0, defaultArgExpr())))); | |
112 | |
113 match_finder->addMatcher(varDecl(forEach(constructor_call)), | |
114 &constructor_callback_); | |
115 match_finder->addMatcher(newExpr(has(constructor_call)), | |
116 &constructor_callback_); | |
117 match_finder->addMatcher(bindTemporaryExpr(has(constructor_call)), | |
118 &temporary_callback_); | |
119 match_finder->addMatcher(constructorDecl(forEach(constructor_call)), | |
120 &initializer_callback_); | |
121 } | |
122 | |
123 void ConstructorCallback::run(const MatchFinder::MatchResult& result) { | |
124 const clang::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); | |
125 if (!IsNullConstant(*arg, result.Context)) | |
126 return; | |
127 | |
128 const clang::CXXConstructExpr* call = | |
129 result.Nodes.getNodeAs<clang::CXXConstructExpr>("call"); | |
130 clang::CharSourceRange range = | |
131 clang::CharSourceRange::getTokenRange(call->getParenRange()); | |
132 replacements_->insert(Replacement(*result.SourceManager, range, "")); | |
133 } | |
134 | |
135 void InitializerCallback::run(const MatchFinder::MatchResult& result) { | |
136 const clang::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); | |
137 if (!IsNullConstant(*arg, result.Context)) | |
138 return; | |
139 | |
140 const clang::CXXConstructExpr* call = | |
141 result.Nodes.getNodeAs<clang::CXXConstructExpr>("call"); | |
142 replacements_->insert(Replacement(*result.SourceManager, call, "")); | |
143 } | |
144 | |
145 void TemporaryCallback::run(const MatchFinder::MatchResult& result) { | |
146 const clang::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); | |
147 if (!IsNullConstant(*arg, result.Context)) | |
148 return; | |
149 | |
150 // TODO(dcheng): File a bug with clang. There should be an easier way to do | |
151 // this replacement, but getTokenRange(call->getParenRange()) and the obvious | |
152 // (but incorrect) arg both don't work. The former is presumably just buggy, | |
153 // while the latter probably has to do with the fact that NULL is actually a | |
154 // macro which expands to a built-in. | |
155 clang::SourceRange range = arg->getSourceRange(); | |
156 clang::SourceRange expansion_range( | |
157 result.SourceManager->getExpansionLoc(range.getBegin()), | |
158 result.SourceManager->getExpansionLoc(range.getEnd())); | |
159 replacements_->insert( | |
160 Replacement(*result.SourceManager, | |
161 clang::CharSourceRange::getTokenRange(expansion_range), | |
162 "")); | |
163 } | |
164 | |
165 } // namespace | |
166 | |
167 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); | |
168 | |
169 int main(int argc, const char* argv[]) { | |
170 CommonOptionsParser options(argc, argv); | |
171 clang::tooling::ClangTool tool(options.getCompilations(), | |
172 options.getSourcePathList()); | |
173 | |
174 Replacements replacements; | |
175 EmptyStringConverter converter(&replacements); | |
176 MatchFinder match_finder; | |
177 converter.SetupMatchers(&match_finder); | |
178 | |
179 int result = | |
180 tool.run(clang::tooling::newFrontendActionFactory(&match_finder)); | |
181 if (result != 0) | |
182 return result; | |
183 | |
184 // Each replacement line should have the following format: | |
185 // r:<file path>:<offset>:<length>:<replacement text> | |
186 // Only the <replacement text> field can contain embedded ":" characters. | |
187 // TODO(dcheng): Use a more clever serialization. | |
188 llvm::outs() << "==== BEGIN EDITS ====\n"; | |
189 for (Replacements::const_iterator it = replacements.begin(); | |
190 it != replacements.end(); | |
191 ++it) { | |
192 llvm::outs() << "r:" << it->getFilePath() << ":" << it->getOffset() << ":" | |
193 << it->getLength() << ":" << it->getReplacementText() << "\n"; | |
194 } | |
195 llvm::outs() << "==== END EDITS ====\n"; | |
196 | |
197 return 0; | |
198 } | |
OLD | NEW |