diff options
author | dcheng@chromium.org <dcheng@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-02 23:46:53 +0000 |
---|---|---|
committer | dcheng@chromium.org <dcheng@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-02 23:46:53 +0000 |
commit | d1a751f18e6591c5d53086da863f8d3421da08f3 (patch) | |
tree | 4ce6861ed1c9958d6ef78bab075a289c54098eb3 /tools/clang/empty_string | |
parent | f78ac522f8fc1463797b4de6831f9f0ee58566f8 (diff) | |
download | chromium_src-d1a751f18e6591c5d53086da863f8d3421da08f3.zip chromium_src-d1a751f18e6591c5d53086da863f8d3421da08f3.tar.gz chromium_src-d1a751f18e6591c5d53086da863f8d3421da08f3.tar.bz2 |
Implement clang tool that converts std::string("") to std::string().
This is intended to be a simple demo of the clang tools functionality.
BUG=
Review URL: https://codereview.chromium.org/12746010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@191936 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/clang/empty_string')
-rw-r--r-- | tools/clang/empty_string/EmptyStringConverter.cpp | 196 | ||||
-rw-r--r-- | tools/clang/empty_string/Makefile | 23 | ||||
-rw-r--r-- | tools/clang/empty_string/tests/test-expected.cc | 38 | ||||
-rw-r--r-- | tools/clang/empty_string/tests/test-original.cc | 38 |
4 files changed, 295 insertions, 0 deletions
diff --git a/tools/clang/empty_string/EmptyStringConverter.cpp b/tools/clang/empty_string/EmptyStringConverter.cpp new file mode 100644 index 0000000..a9cc4b0 --- /dev/null +++ b/tools/clang/empty_string/EmptyStringConverter.cpp @@ -0,0 +1,196 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This implements a Clang tool to convert all instances of std::string("") to +// std::string(). The latter is more efficient (as std::string doesn't have to +// take a copy of an empty string) and generates fewer instructions as well. It +// should be run using the tools/clang/scripts/run_tool.py helper. + +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" + +using clang::ast_matchers::MatchFinder; +using clang::ast_matchers::argumentCountIs; +using clang::ast_matchers::bindTemporaryExpr; +using clang::ast_matchers::constructorDecl; +using clang::ast_matchers::constructExpr; +using clang::ast_matchers::defaultArgExpr; +using clang::ast_matchers::expr; +using clang::ast_matchers::forEach; +using clang::ast_matchers::has; +using clang::ast_matchers::hasArgument; +using clang::ast_matchers::hasDeclaration; +using clang::ast_matchers::hasName; +using clang::ast_matchers::id; +using clang::ast_matchers::methodDecl; +using clang::ast_matchers::newExpr; +using clang::ast_matchers::ofClass; +using clang::ast_matchers::stringLiteral; +using clang::ast_matchers::varDecl; +using clang::tooling::CommonOptionsParser; +using clang::tooling::Replacement; +using clang::tooling::Replacements; + +namespace { + +// Handles replacements for stack and heap-allocated instances, e.g.: +// std::string a(""); +// std::string* b = new std::string(""); +class ConstructorCallback : public MatchFinder::MatchCallback { + public: + ConstructorCallback(Replacements* replacements) + : replacements_(replacements) {} + + virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; + + private: + Replacements* const replacements_; +}; + +// Handles replacements for invocations of std::string("") in an initializer +// list. +class InitializerCallback : public MatchFinder::MatchCallback { + public: + InitializerCallback(Replacements* replacements) + : replacements_(replacements) {} + + virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; + + private: + Replacements* const replacements_; +}; + +// Handles replacements for invocations of std::string("") in a temporary +// context, e.g. FunctionThatTakesString(std::string("")). Note that this +// handles implicits construction of std::string as well. +class TemporaryCallback : public MatchFinder::MatchCallback { + public: + TemporaryCallback(Replacements* replacements) : replacements_(replacements) {} + + virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE; + + private: + Replacements* const replacements_; +}; + +class EmptyStringConverter { + public: + explicit EmptyStringConverter(Replacements* replacements) + : constructor_callback_(replacements), + initializer_callback_(replacements), + temporary_callback_(replacements) {} + + void SetupMatchers(MatchFinder* match_finder); + + private: + ConstructorCallback constructor_callback_; + InitializerCallback initializer_callback_; + TemporaryCallback temporary_callback_; +}; + +void EmptyStringConverter::SetupMatchers(MatchFinder* match_finder) { + const auto& constructor_call = + id("call", + constructExpr( + hasDeclaration(methodDecl(ofClass(hasName("std::basic_string")))), + argumentCountIs(2), + hasArgument(0, id("literal", stringLiteral())), + hasArgument(1, defaultArgExpr()))); + + // Note that expr(has()) in the matcher is significant; the Clang AST wraps + // calls to the std::string constructor with exprWithCleanups nodes. Without + // the expr(has()) matcher, the first and last rules would not match anything! + match_finder->addMatcher(varDecl(forEach(expr(has(constructor_call)))), + &constructor_callback_); + match_finder->addMatcher(newExpr(has(constructor_call)), + &constructor_callback_); + match_finder->addMatcher(bindTemporaryExpr(has(constructor_call)), + &temporary_callback_); + match_finder->addMatcher( + constructorDecl(forEach(expr(has(constructor_call)))), + &initializer_callback_); +} + +void ConstructorCallback::run(const MatchFinder::MatchResult& result) { + const clang::StringLiteral* literal = + result.Nodes.getNodeAs<clang::StringLiteral>("literal"); + if (literal->getLength() > 0) + return; + + const clang::CXXConstructExpr* call = + result.Nodes.getNodeAs<clang::CXXConstructExpr>("call"); + clang::CharSourceRange range = + clang::CharSourceRange::getTokenRange(call->getParenRange()); + replacements_->insert(Replacement(*result.SourceManager, range, "")); +} + +void InitializerCallback::run(const MatchFinder::MatchResult& result) { + const clang::StringLiteral* literal = + result.Nodes.getNodeAs<clang::StringLiteral>("literal"); + if (literal->getLength() > 0) + return; + + const clang::CXXConstructExpr* call = + result.Nodes.getNodeAs<clang::CXXConstructExpr>("call"); + replacements_->insert(Replacement(*result.SourceManager, call, "")); +} + +void TemporaryCallback::run(const MatchFinder::MatchResult& result) { + const clang::StringLiteral* literal = + result.Nodes.getNodeAs<clang::StringLiteral>("literal"); + if (literal->getLength() > 0) + return; + + const clang::CXXConstructExpr* call = + result.Nodes.getNodeAs<clang::CXXConstructExpr>("call"); + // Differentiate between explicit and implicit calls to std::string's + // constructor. An implicitly generated constructor won't have a valid + // source range for the parenthesis. + clang::SourceRange range = call->getParenRange(); + if (range.isValid()) { + replacements_->insert(Replacement(*result.SourceManager, literal, "")); + } else { + replacements_->insert( + Replacement(*result.SourceManager, call, "std::string()")); + } +} + +} // namespace + +static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); + +int main(int argc, const char* argv[]) { + CommonOptionsParser options(argc, argv); + clang::tooling::ClangTool tool(options.getCompilations(), + options.getSourcePathList()); + + Replacements replacements; + EmptyStringConverter converter(&replacements); + MatchFinder match_finder; + converter.SetupMatchers(&match_finder); + + int result = + tool.run(clang::tooling::newFrontendActionFactory(&match_finder)); + if (result != 0) + return result; + + // Each replacement line should have the following format: + // r:<file path>:<offset>:<length>:<replacement text> + // Only the <replacement text> field can contain embedded ":" characters. + // TODO(dcheng): Use a more clever serialization. + llvm::outs() << "==== BEGIN EDITS ====\n"; + for (const Replacement& r : replacements) { + llvm::outs() << "r:" << r.getFilePath() << ":" << r.getOffset() << ":" + << r.getLength() << ":" << r.getReplacementText() << "\n"; + } + llvm::outs() << "==== END EDITS ====\n"; + + return 0; +} diff --git a/tools/clang/empty_string/Makefile b/tools/clang/empty_string/Makefile new file mode 100644 index 0000000..d446412 --- /dev/null +++ b/tools/clang/empty_string/Makefile @@ -0,0 +1,23 @@ +# Copyright (c) 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# This Makefile requires the LLVM build system. In order to build this tool, +# please run tools/clang/scripts/build_tool.py. + +CLANG_LEVEL := ../.. + +TOOLNAME = empty_string + +NO_INSTALL = 1 + +include $(CLANG_LEVEL)/../../Makefile.config + +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc +USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ + clangRewriteFrontend.a clangRewriteCore.a clangParse.a clangSema.a \ + clangAnalysis.a clangAST.a clangASTMatchers.a clangEdit.a \ + clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/Makefile + diff --git a/tools/clang/empty_string/tests/test-expected.cc b/tools/clang/empty_string/tests/test-expected.cc new file mode 100644 index 0000000..88877b1 --- /dev/null +++ b/tools/clang/empty_string/tests/test-expected.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Test file for the empty string clang tool. + +#include <string> + +// Tests for std::string declarations. +void TestDeclarations() { std::string a, b("abc"), c; } + +// Tests for std::string allocated with new. +void TestNew() { + std::string* a = new std::string, + *b = new std::string("abc"), + *c = new std::string, + *d = new std::string(); +} + +// Tests for std::string construction in initializer lists. +class TestInitializers { + public: + TestInitializers() {} + TestInitializers(bool) {} + TestInitializers(double) : b("cat"), c() {} + + private: + std::string a; + std::string b; + std::string c; +}; + +// Tests for temporary std::strings. +void TestTemporaries(const std::string& reference_argument, + const std::string value_argument) { + TestTemporaries(std::string(), std::string()); + TestTemporaries(std::string(), std::string()); +} diff --git a/tools/clang/empty_string/tests/test-original.cc b/tools/clang/empty_string/tests/test-original.cc new file mode 100644 index 0000000..f86fd08 --- /dev/null +++ b/tools/clang/empty_string/tests/test-original.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Test file for the empty string clang tool. + +#include <string> + +// Tests for std::string declarations. +void TestDeclarations() { std::string a(""), b("abc"), c(""); } + +// Tests for std::string allocated with new. +void TestNew() { + std::string* a = new std::string(""), + *b = new std::string("abc"), + *c = new std::string(""), + *d = new std::string(); +} + +// Tests for std::string construction in initializer lists. +class TestInitializers { + public: + TestInitializers() : a("") {} + TestInitializers(bool) : a(""), b("") {} + TestInitializers(double) : a(""), b("cat"), c() {} + + private: + std::string a; + std::string b; + std::string c; +}; + +// Tests for temporary std::strings. +void TestTemporaries(const std::string& reference_argument, + const std::string value_argument) { + TestTemporaries("", ""); + TestTemporaries(std::string(""), std::string("")); +} |