• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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