1 // Copyright 2016 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 #include "ListValueRewriter.h"
6
7 #include <assert.h>
8 #include <algorithm>
9
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/ParentMap.h"
12 #include "clang/AST/RecursiveASTVisitor.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/ASTMatchers/ASTMatchers.h"
15 #include "clang/ASTMatchers/ASTMatchersMacros.h"
16 #include "clang/Basic/CharInfo.h"
17 #include "clang/Basic/SourceManager.h"
18 #include "clang/Frontend/FrontendActions.h"
19 #include "clang/Lex/Lexer.h"
20 #include "clang/Tooling/Refactoring.h"
21 #include "llvm/ADT/STLExtras.h"
22
23 using namespace clang::ast_matchers;
24 using clang::tooling::Replacement;
25 using llvm::StringRef;
26
27 namespace {
28
29 // Helper class for AppendRawCallback to visit each DeclRefExpr for a given
30 // VarDecl. If it finds a DeclRefExpr it can't figure out how to rewrite, the
31 // traversal will be terminated early.
32 class CollectDeclRefExprVisitor
33 : public clang::RecursiveASTVisitor<CollectDeclRefExprVisitor> {
34 public:
CollectDeclRefExprVisitor(clang::SourceManager * source_manager,clang::ASTContext * ast_context,const clang::VarDecl * decl,const clang::FunctionDecl * containing_function)35 CollectDeclRefExprVisitor(clang::SourceManager* source_manager,
36 clang::ASTContext* ast_context,
37 const clang::VarDecl* decl,
38 const clang::FunctionDecl* containing_function)
39 : source_manager_(source_manager),
40 ast_context_(ast_context),
41 decl_(decl),
42 is_valid_(decl->hasInit()),
43 map_(containing_function->getBody()) {}
44
45 // RecursiveASTVisitor:
VisitDeclRefExpr(const clang::DeclRefExpr * expr)46 bool VisitDeclRefExpr(const clang::DeclRefExpr* expr) {
47 if (expr->getDecl() != decl_)
48 return true;
49
50 const clang::Stmt* stmt = expr;
51 while (stmt) {
52 // TODO(dcheng): Add a const version of getParentIgnoreParenImpCasts.
53 stmt = map_.getParentIgnoreParenImpCasts(const_cast<clang::Stmt*>(stmt));
54
55 if (clang::isa<clang::MemberExpr>(stmt)) {
56 // Member expressions need no special rewriting since std::unique_ptr
57 // overloads `.' and `->'.
58 return is_valid_;
59 } else if (auto* member_call_expr =
60 clang::dyn_cast<clang::CXXMemberCallExpr>(stmt)) {
61 return HandleMemberCallExpr(member_call_expr, expr);
62 } else if (auto* binary_op =
63 clang::dyn_cast<clang::BinaryOperator>(stmt)) {
64 return HandleBinaryOp(binary_op);
65 } else {
66 // Can't handle this so cancel the rewrite.
67 stmt->dump();
68 return false;
69 }
70 }
71
72 assert(false);
73 return false;
74 }
75
replacements() const76 const std::set<clang::tooling::Replacement>& replacements() const {
77 return replacements_;
78 }
79
80 private:
HandleMemberCallExpr(const clang::CXXMemberCallExpr * member_call_expr,const clang::DeclRefExpr * decl_ref_expr)81 bool HandleMemberCallExpr(const clang::CXXMemberCallExpr* member_call_expr,
82 const clang::DeclRefExpr* decl_ref_expr) {
83 // If this isn't a ListValue::Append() call, cancel the rewrite: it
84 // will require manual inspection to determine if it's an ownership
85 // transferring call or not.
86 auto* method_decl = member_call_expr->getMethodDecl();
87 if (method_decl->getQualifiedNameAsString() != "base::ListValue::Append")
88 return false;
89 // Use-after-move is also a fatal error.
90 if (!is_valid_)
91 return false;
92
93 is_valid_ = false;
94
95 // Surround the DeclRefExpr with std::move().
96 replacements_.emplace(*source_manager_, decl_ref_expr->getLocStart(), 0,
97 "std::move(");
98
99 clang::SourceLocation end = clang::Lexer::getLocForEndOfToken(
100 decl_ref_expr->getLocEnd(), 0, *source_manager_,
101 ast_context_->getLangOpts());
102 replacements_.emplace(*source_manager_, end, 0, ")");
103 return true;
104 }
105
HandleBinaryOp(const clang::BinaryOperator * op)106 bool HandleBinaryOp(const clang::BinaryOperator* op) {
107 if (op->isRelationalOp() || op->isEqualityOp() || op->isLogicalOp()) {
108 // Supported binary operations for which no rewrites need to be done.
109 return is_valid_;
110 }
111 if (!op->isAssignmentOp()) {
112 // Pointer arithmetic or something else clever. Just cancel the rewrite.
113 return false;
114 }
115 if (op->isCompoundAssignmentOp()) {
116 // +=, -=, etc. Give up and cancel the rewrite.
117 return false;
118 }
119
120 const clang::Expr* rhs = op->getRHS()->IgnoreParenImpCasts();
121 const clang::CXXNewExpr* new_expr = clang::dyn_cast<clang::CXXNewExpr>(rhs);
122 if (!new_expr) {
123 // The variable isn't being assigned the result of a new operation. Just
124 // cancel the rewrite.
125 return false;
126 }
127
128 is_valid_ = true;
129
130 // Rewrite the assignment operation to use std::unique_ptr::reset().
131 clang::CharSourceRange range = clang::CharSourceRange::getCharRange(
132 op->getOperatorLoc(), op->getRHS()->getLocStart());
133 replacements_.emplace(*source_manager_, range, ".reset(");
134
135 clang::SourceLocation expr_end = clang::Lexer::getLocForEndOfToken(
136 op->getLocEnd(), 0, *source_manager_, ast_context_->getLangOpts());
137 replacements_.emplace(*source_manager_, expr_end, 0, ")");
138 return true;
139 }
140
141 clang::SourceManager* const source_manager_;
142 clang::ASTContext* const ast_context_;
143 const clang::VarDecl* const decl_;
144 // Tracks the state of |decl_| during the traversal. |decl_| becomes valid
145 // upon initialization/assignment and becomes invalid when passed as an
146 // argument to base::ListValue::Append(base::Value*).
147 bool is_valid_;
148 clang::ParentMap map_;
149 std::set<clang::tooling::Replacement> replacements_;
150 };
151
152 } // namespace
153
AppendCallback(std::set<clang::tooling::Replacement> * replacements)154 ListValueRewriter::AppendCallback::AppendCallback(
155 std::set<clang::tooling::Replacement>* replacements)
156 : replacements_(replacements) {}
157
run(const MatchFinder::MatchResult & result)158 void ListValueRewriter::AppendCallback::run(
159 const MatchFinder::MatchResult& result) {
160 // Delete `new base::*Value(' and `)'.
161 auto* newExpr = result.Nodes.getNodeAs<clang::CXXNewExpr>("newExpr");
162 auto* argExpr = result.Nodes.getNodeAs<clang::Expr>("argExpr");
163
164 // Note that for the end loc, we use the expansion loc: the argument might be
165 // a macro like true and false.
166 clang::CharSourceRange pre_arg_range = clang::CharSourceRange::getCharRange(
167 newExpr->getLocStart(),
168 result.SourceManager->getExpansionLoc(argExpr->getLocStart()));
169 replacements_->emplace(*result.SourceManager, pre_arg_range, "");
170
171 clang::CharSourceRange post_arg_range =
172 clang::CharSourceRange::getTokenRange(newExpr->getLocEnd());
173 replacements_->emplace(*result.SourceManager, post_arg_range, "");
174 }
175
AppendBooleanCallback(std::set<clang::tooling::Replacement> * replacements)176 ListValueRewriter::AppendBooleanCallback::AppendBooleanCallback(
177 std::set<clang::tooling::Replacement>* replacements)
178 : AppendCallback(replacements) {}
179
run(const MatchFinder::MatchResult & result)180 void ListValueRewriter::AppendBooleanCallback::run(
181 const MatchFinder::MatchResult& result) {
182 // Replace 'Append' with 'AppendBoolean'.
183 auto* callExpr = result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("callExpr");
184
185 clang::CharSourceRange call_range =
186 clang::CharSourceRange::getTokenRange(callExpr->getExprLoc());
187 replacements_->emplace(*result.SourceManager, call_range, "AppendBoolean");
188
189 AppendCallback::run(result);
190 }
191
AppendIntegerCallback(std::set<clang::tooling::Replacement> * replacements)192 ListValueRewriter::AppendIntegerCallback::AppendIntegerCallback(
193 std::set<clang::tooling::Replacement>* replacements)
194 : AppendCallback(replacements) {}
195
run(const MatchFinder::MatchResult & result)196 void ListValueRewriter::AppendIntegerCallback::run(
197 const MatchFinder::MatchResult& result) {
198 // Replace 'Append' with 'AppendInteger'.
199 auto* callExpr = result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("callExpr");
200
201 clang::CharSourceRange call_range =
202 clang::CharSourceRange::getTokenRange(callExpr->getExprLoc());
203 replacements_->emplace(*result.SourceManager, call_range, "AppendInteger");
204
205 AppendCallback::run(result);
206 }
207
AppendDoubleCallback(std::set<clang::tooling::Replacement> * replacements)208 ListValueRewriter::AppendDoubleCallback::AppendDoubleCallback(
209 std::set<clang::tooling::Replacement>* replacements)
210 : AppendCallback(replacements) {}
211
run(const MatchFinder::MatchResult & result)212 void ListValueRewriter::AppendDoubleCallback::run(
213 const MatchFinder::MatchResult& result) {
214 // Replace 'Append' with 'AppendDouble'.
215 auto* callExpr = result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("callExpr");
216
217 clang::CharSourceRange call_range =
218 clang::CharSourceRange::getTokenRange(callExpr->getExprLoc());
219 replacements_->emplace(*result.SourceManager, call_range, "AppendDouble");
220
221 AppendCallback::run(result);
222 }
223
AppendStringCallback(std::set<clang::tooling::Replacement> * replacements)224 ListValueRewriter::AppendStringCallback::AppendStringCallback(
225 std::set<clang::tooling::Replacement>* replacements)
226 : AppendCallback(replacements) {}
227
run(const MatchFinder::MatchResult & result)228 void ListValueRewriter::AppendStringCallback::run(
229 const MatchFinder::MatchResult& result) {
230 // Replace 'Append' with 'AppendString'.
231 auto* callExpr = result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("callExpr");
232
233 clang::CharSourceRange call_range =
234 clang::CharSourceRange::getTokenRange(callExpr->getExprLoc());
235 replacements_->emplace(*result.SourceManager, call_range, "AppendString");
236
237 AppendCallback::run(result);
238 }
239
240 ListValueRewriter::AppendReleasedUniquePtrCallback::
AppendReleasedUniquePtrCallback(std::set<clang::tooling::Replacement> * replacements)241 AppendReleasedUniquePtrCallback(
242 std::set<clang::tooling::Replacement>* replacements)
243 : replacements_(replacements) {}
244
run(const MatchFinder::MatchResult & result)245 void ListValueRewriter::AppendReleasedUniquePtrCallback::run(
246 const MatchFinder::MatchResult& result) {
247 auto* object_expr = result.Nodes.getNodeAs<clang::Expr>("objectExpr");
248 bool arg_is_rvalue = object_expr->Classify(*result.Context).isRValue();
249
250 // Remove .release()
251 auto* member_call =
252 result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("memberCall");
253 auto* member_expr = result.Nodes.getNodeAs<clang::MemberExpr>("memberExpr");
254 clang::CharSourceRange release_range = clang::CharSourceRange::getTokenRange(
255 member_expr->getOperatorLoc(), member_call->getLocEnd());
256 replacements_->emplace(*result.SourceManager, release_range,
257 arg_is_rvalue ? "" : ")");
258
259 if (arg_is_rvalue)
260 return;
261
262 // Insert `std::move(' for non-rvalue expressions.
263 clang::CharSourceRange insertion_range = clang::CharSourceRange::getCharRange(
264 object_expr->getLocStart(), object_expr->getLocStart());
265 replacements_->emplace(*result.SourceManager, insertion_range, "std::move(");
266 }
267
AppendRawPtrCallback(std::set<clang::tooling::Replacement> * replacements)268 ListValueRewriter::AppendRawPtrCallback::AppendRawPtrCallback(
269 std::set<clang::tooling::Replacement>* replacements)
270 : replacements_(replacements) {}
271
run(const MatchFinder::MatchResult & result)272 void ListValueRewriter::AppendRawPtrCallback::run(
273 const MatchFinder::MatchResult& result) {
274 auto* var_decl = result.Nodes.getNodeAs<clang::VarDecl>("varDecl");
275 // As an optimization, skip processing if it's already been visited, since
276 // this match callback walks the entire function body.
277 if (visited_.find(var_decl) != visited_.end())
278 return;
279 visited_.insert(var_decl);
280 auto* function_context = var_decl->getParentFunctionOrMethod();
281 assert(function_context && "local var not in function context?!");
282 auto* function_decl = clang::cast<clang::FunctionDecl>(function_context);
283
284 auto* type_source_info = var_decl->getTypeSourceInfo();
285 assert(type_source_info && "no type source info for VarDecl?!");
286 // Don't bother trying to handle qualifiers.
287 clang::QualType qual_type = var_decl->getType();
288 if (qual_type.hasQualifiers()) {
289 return;
290 }
291
292 CollectDeclRefExprVisitor visitor(result.SourceManager, result.Context,
293 var_decl, function_decl);
294 if (!visitor.TraverseStmt(function_decl->getBody()))
295 return;
296
297 // Rewrite the variable type to use std::unique_ptr.
298 clang::CharSourceRange type_range = clang::CharSourceRange::getTokenRange(
299 type_source_info->getTypeLoc().getSourceRange());
300 std::string replacement_type = "std::unique_ptr<";
301 while (true) {
302 const clang::Type* type = qual_type.getTypePtr();
303 if (auto* auto_type = type->getAs<clang::AutoType>()) {
304 if (!auto_type->isDeduced()) {
305 // If an AutoType isn't deduced, the rewriter can't do anything.
306 return;
307 }
308 qual_type = auto_type->getDeducedType();
309 } else if (auto* pointer_type = type->getAs<clang::PointerType>()) {
310 qual_type = pointer_type->getPointeeType();
311 } else {
312 break;
313 }
314 }
315 replacement_type += qual_type.getAsString();
316 replacement_type += ">";
317 replacements_->emplace(*result.SourceManager, type_range, replacement_type);
318
319 // Initialized with `='
320 if (var_decl->hasInit() &&
321 var_decl->getInitStyle() == clang::VarDecl::CInit) {
322 clang::SourceLocation name_end = clang::Lexer::getLocForEndOfToken(
323 var_decl->getLocation(), 0, *result.SourceManager,
324 result.Context->getLangOpts());
325 clang::CharSourceRange range = clang::CharSourceRange::getCharRange(
326 name_end, var_decl->getInit()->getLocStart());
327 replacements_->emplace(*result.SourceManager, range, "(");
328
329 clang::SourceLocation init_end = clang::Lexer::getLocForEndOfToken(
330 var_decl->getInit()->getLocEnd(), 0, *result.SourceManager,
331 result.Context->getLangOpts());
332 replacements_->emplace(*result.SourceManager, init_end, 0, ")");
333 }
334
335 // Also append the collected replacements from visiting the DeclRefExprs.
336 replacements_->insert(visitor.replacements().begin(),
337 visitor.replacements().end());
338 }
339
ListValueRewriter(std::set<clang::tooling::Replacement> * replacements)340 ListValueRewriter::ListValueRewriter(
341 std::set<clang::tooling::Replacement>* replacements)
342 : append_boolean_callback_(replacements),
343 append_integer_callback_(replacements),
344 append_double_callback_(replacements),
345 append_string_callback_(replacements),
346 append_released_unique_ptr_callback_(replacements),
347 append_raw_ptr_callback_(replacements) {}
348
RegisterMatchers(MatchFinder * match_finder)349 void ListValueRewriter::RegisterMatchers(MatchFinder* match_finder) {
350 auto is_list_append = cxxMemberCallExpr(
351 callee(cxxMethodDecl(hasName("::base::ListValue::Append"))),
352 argumentCountIs(1));
353
354 // base::ListValue::Append(new base::FundamentalValue(bool))
355 // => base::ListValue::AppendBoolean()
356 match_finder->addMatcher(
357 id("callExpr",
358 cxxMemberCallExpr(
359 is_list_append,
360 hasArgument(
361 0, ignoringParenImpCasts(id(
362 "newExpr",
363 cxxNewExpr(has(cxxConstructExpr(
364 hasDeclaration(cxxMethodDecl(hasName(
365 "::base::FundamentalValue::FundamentalValue"))),
366 argumentCountIs(1),
367 hasArgument(
368 0, id("argExpr",
369 expr(hasType(booleanType())))))))))))),
370 &append_boolean_callback_);
371
372 // base::ListValue::Append(new base::FundamentalValue(int))
373 // => base::ListValue::AppendInteger()
374 match_finder->addMatcher(
375 id("callExpr",
376 cxxMemberCallExpr(
377 is_list_append,
378 hasArgument(
379 0,
380 ignoringParenImpCasts(id(
381 "newExpr",
382 cxxNewExpr(has(cxxConstructExpr(
383 hasDeclaration(cxxMethodDecl(hasName(
384 "::base::FundamentalValue::FundamentalValue"))),
385 argumentCountIs(1),
386 hasArgument(0, id("argExpr",
387 expr(hasType(isInteger()),
388 unless(hasType(
389 booleanType()))))))))))))),
390 &append_integer_callback_);
391
392 // base::ListValue::Append(new base::FundamentalValue(double))
393 // => base::ListValue::AppendDouble()
394 match_finder->addMatcher(
395 id("callExpr",
396 cxxMemberCallExpr(
397 is_list_append,
398 hasArgument(
399 0, ignoringParenImpCasts(id(
400 "newExpr",
401 cxxNewExpr(has(cxxConstructExpr(
402 hasDeclaration(cxxMethodDecl(hasName(
403 "::base::FundamentalValue::FundamentalValue"))),
404 argumentCountIs(1),
405 hasArgument(
406 0, id("argExpr",
407 expr(hasType(
408 realFloatingPointType())))))))))))),
409 &append_double_callback_);
410
411 // base::ListValue::Append(new base::StringValue(...))
412 // => base::ListValue::AppendString()
413 match_finder->addMatcher(
414 id("callExpr",
415 cxxMemberCallExpr(
416 is_list_append,
417 hasArgument(
418 0, ignoringParenImpCasts(id(
419 "newExpr",
420 cxxNewExpr(has(cxxConstructExpr(
421 hasDeclaration(cxxMethodDecl(
422 hasName("::base::StringValue::StringValue"))),
423 argumentCountIs(1),
424 hasArgument(0, id("argExpr", expr())))))))))),
425 &append_string_callback_);
426
427 auto is_unique_ptr_release =
428 allOf(callee(cxxMethodDecl(
429 hasName("release"),
430 ofClass(cxxRecordDecl(hasName("::std::unique_ptr"))))),
431 argumentCountIs(0));
432
433 // base::ListValue::Append(ReturnsUniquePtr().release())
434 // => base::ListValue::Append(ReturnsUniquePtr())
435 // or
436 // base::ListValue::Append(unique_ptr_var.release())
437 // => base::ListValue::Append(std::move(unique_ptr_var))
438 match_finder->addMatcher(
439 cxxMemberCallExpr(
440 is_list_append,
441 hasArgument(
442 0, ignoringParenImpCasts(
443 id("memberCall",
444 cxxMemberCallExpr(has(id("memberExpr", memberExpr())),
445 is_unique_ptr_release,
446 on(id("objectExpr", expr()))))))),
447 &append_released_unique_ptr_callback_);
448
449 // Simple versions of the following pattern. Note the callback itself does
450 // much of the filtering (to detect use-after-move, things that aren't
451 // assigned the result of a new expression, etc).
452 //
453 // base::ListValue* this_list = new base::ListValue;
454 // this_list->AppendInteger(1);
455 // that_list->Append(this_list);
456 //
457 // will be rewritten to
458 //
459 // std::unique_ptr<base::ListValue> this_list(new base::ListValue);
460 // this_list->AppendInteger(1);
461 // that_list->Append(std::move(this_list);
462 match_finder->addMatcher(
463 cxxMemberCallExpr(
464 is_list_append,
465 hasArgument(
466 0,
467 ignoringParenImpCasts(id(
468 "declRefExpr",
469 declRefExpr(to(id(
470 "varDecl",
471 varDecl(
472 hasLocalStorage(),
473 anyOf(hasInitializer(
474 // Note this won't match C++11 uniform
475 // initialization syntax, since the
476 // CXXNewExpr is wrapped in an
477 // InitListExpr in that case.
478 ignoringParenImpCasts(cxxNewExpr())),
479 unless(hasInitializer(expr()))),
480 unless(parmVarDecl()))))))))),
481 &append_raw_ptr_callback_);
482 }
483