Add rewriter of data members to getter/setter member functions
This CL adds a Clang rewriter that converts references to a data
member to calls of a getter or setter. The intended use-case is to
convert a struct with public members to a class with getters and
setters.
Specifically, it's intended to rewrite the structs FormData and
FormFieldData, starting with FormFieldData::value.
The rewriter does the following conversion, where `foo` is a
`FormFieldData`:
Old: `foo.value = bar` `foo->value = bar`
New: `foo.set_value(bar)` `foo->set_value(bar)`
Outside of the LHS of an assignment:
Old: `foo.value` `foo->value`
New: `foo.value()` `foo->value()`
Old: `::testing::Field(&FormFieldData::value, bar)`
New: `::testing::Property(&FormFieldData::value, bar)`
Followup CLs will extend the rewriter to other public data members
of FormFieldData and add another rewriter for its parent struct
FormData.
Bug: 40232021
Change-Id: Iaad5042514f6deafe3ee3f6a95f428bfb216508a
Cq-Do-Not-Cancel-Tryjobs: true
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5422009
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Christoph Schwering <schwering@google.com>
Cr-Commit-Position: refs/heads/main@{#1288878}
NOKEYCHECK=True
GitOrigin-RevId: 42c36aeee2de84c1ef21ef767114b56a7e265c0c
diff --git a/rewrite_autofill_form_data/CMakeLists.txt b/rewrite_autofill_form_data/CMakeLists.txt
new file mode 100644
index 0000000..619504d
--- /dev/null
+++ b/rewrite_autofill_form_data/CMakeLists.txt
@@ -0,0 +1,28 @@
+set(LLVM_LINK_COMPONENTS
+ BitReader
+ MCParser
+ Option
+ X86AsmParser
+ X86CodeGen
+ )
+
+add_llvm_executable(rewrite_autofill_form_data
+ FieldToFunction.cpp
+ )
+
+target_link_libraries(rewrite_autofill_form_data
+ clangAST
+ clangASTMatchers
+ clangAnalysis
+ clangBasic
+ clangDriver
+ clangEdit
+ clangFrontend
+ clangLex
+ clangParse
+ clangSema
+ clangSerialization
+ clangTooling
+ )
+
+cr_install(TARGETS rewrite_autofill_form_data RUNTIME DESTINATION bin)
diff --git a/rewrite_autofill_form_data/FieldToFunction.cpp b/rewrite_autofill_form_data/FieldToFunction.cpp
new file mode 100644
index 0000000..9cd03c2
--- /dev/null
+++ b/rewrite_autofill_form_data/FieldToFunction.cpp
@@ -0,0 +1,413 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Clang tool to change accesses to data members to calling getters or setters.
+//
+// For example, if `foo` and `bar` are of a type whose `member` is supposed to
+// be rewritten, the rewriter replaces `foo.member = bar.member` with
+// `foo.set_member(bar.member())`.
+//
+// In particular, this is used to rewrite autofill::FormFieldData::value.
+
+#include <cassert>
+#include <iostream>
+#include <memory>
+#include <regex>
+#include <string>
+
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersMacros.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/TargetSelect.h"
+
+// Prints a clang::SourceLocation or clang::SourceRange.
+#define LOG(e) \
+ llvm::errs() << __FILE__ << ":" << __LINE__ << ": " << #e << " " \
+ << (e).printToString(*result.SourceManager) << '\n';
+
+namespace {
+
+llvm::cl::extrahelp common_help(
+ clang::tooling::CommonOptionsParser::HelpMessage);
+llvm::cl::extrahelp more_help(
+ "The rewriter turns references to ClassOfInterest::field_of_interest into\n"
+ "getter and setter calls.");
+llvm::cl::OptionCategory rewriter_category("Rewriter Options");
+llvm::cl::opt<std::string> classes_of_interest_option(
+ "classes-of-interest",
+ llvm::cl::desc("Comma-separated fully qualified names of the classes whose "
+ "field are to be rewritten"),
+ llvm::cl::init("::autofill::FormFieldData"),
+ llvm::cl::cat(rewriter_category));
+llvm::cl::opt<std::string> fields_of_interest_option(
+ "fields-of-interest",
+ llvm::cl::desc("Comma-separated names of data members of any class of "
+ "interest that are to be rewritten"),
+ llvm::cl::init("value"),
+ llvm::cl::cat(rewriter_category));
+
+// Splits a string at each comma.
+// For example, `Explode("foo,bar,")` returns `{"foo", "bar", ""}`.
+std::vector<std::string> Explode(std::string_view s) {
+ std::vector<std::string> substrings;
+ for (;;) {
+ size_t pos = s.find(',');
+ substrings.push_back(std::string(s.substr(0, pos)));
+ if (pos == std::string::npos) {
+ break;
+ }
+ s = s.substr(pos + 1);
+ }
+ return substrings;
+}
+
+// Generates substitution directives.
+// We do not use `clang::tooling::Replacements` because it's restricted to
+// same-file substitutions.
+class OutputHelper : public clang::tooling::SourceFileCallbacks {
+ public:
+ OutputHelper() = default;
+ ~OutputHelper() = default;
+
+ OutputHelper(const OutputHelper&) = delete;
+ OutputHelper& operator=(const OutputHelper&) = delete;
+
+ // Deletes `replacement_range`.
+ void Delete(const clang::CharSourceRange& replacement_range,
+ const clang::SourceManager& source_manager,
+ const clang::LangOptions& lang_opts) {
+ Replace(replacement_range, "", source_manager, lang_opts);
+ }
+
+ // Replaces `replacement_range` with `replacement_text`.
+ void Replace(const clang::CharSourceRange& replacement_range,
+ std::string replacement_text,
+ const clang::SourceManager& source_manager,
+ const clang::LangOptions& lang_opts) {
+ clang::tooling::Replacement replacement(source_manager, replacement_range,
+ replacement_text, lang_opts);
+ llvm::StringRef file_path = replacement.getFilePath();
+ if (file_path.empty()) {
+ return;
+ }
+ std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0');
+ Add(file_path, replacement.getOffset(), replacement.getLength(),
+ replacement_text);
+ }
+
+ // Inserts `lhs` and `rhs` to the left and right of `replacement_range`.
+ void Wrap(const clang::CharSourceRange& replacement_range,
+ std::string_view lhs,
+ std::string_view rhs,
+ const clang::SourceManager& source_manager,
+ const clang::LangOptions& lang_opts) {
+ clang::tooling::Replacement replacement(source_manager, replacement_range,
+ "", lang_opts);
+ llvm::StringRef file_path = replacement.getFilePath();
+ if (file_path.empty()) {
+ return;
+ }
+ Add(file_path, replacement.getOffset(), 0, lhs);
+ Add(file_path, replacement.getOffset() + replacement.getLength(), 0, rhs);
+ }
+
+ private:
+ // clang::tooling::SourceFileCallbacks:
+ bool handleBeginSource(clang::CompilerInstance& compiler) override {
+ const clang::FrontendOptions& frontend_options = compiler.getFrontendOpts();
+
+ assert((frontend_options.Inputs.size() == 1) &&
+ "run_tool.py should invoke the rewriter one file at a time");
+ const clang::FrontendInputFile& input_file = frontend_options.Inputs[0];
+ assert(input_file.isFile() &&
+ "run_tool.py should invoke the rewriter on actual files");
+
+ current_language_ = input_file.getKind().getLanguage();
+
+ if (ShouldOutput()) {
+ llvm::outs() << "==== BEGIN EDITS ====\n";
+ }
+ return true; // Report that |handleBeginSource| succeeded.
+ }
+
+ void handleEndSource() override {
+ if (ShouldOutput()) {
+ llvm::outs() << "==== END EDITS ====\n";
+ }
+ }
+
+ void Add(llvm::StringRef file_path,
+ unsigned offset,
+ unsigned length,
+ std::string_view replacement_text) {
+ if (ShouldOutput()) {
+ llvm::outs() << "r:::" << file_path << ":::" << offset << ":::" << length
+ << ":::" << replacement_text << "\n";
+ }
+ }
+
+ bool ShouldOutput() {
+ switch (current_language_) {
+ case clang::Language::CXX:
+ case clang::Language::OpenCLCXX:
+ case clang::Language::ObjCXX:
+ return true;
+ case clang::Language::Unknown:
+ case clang::Language::Asm:
+ case clang::Language::LLVM_IR:
+ case clang::Language::CIR:
+ case clang::Language::OpenCL:
+ case clang::Language::CUDA:
+ case clang::Language::RenderScript:
+ case clang::Language::HIP:
+ case clang::Language::HLSL:
+ case clang::Language::C:
+ case clang::Language::ObjC:
+ return false;
+ }
+ assert(false && "Unrecognized clang::Language");
+ return false;
+ }
+
+ std::vector<std::string> output_lines_;
+ clang::Language current_language_ = clang::Language::Unknown;
+};
+
+namespace matchers {
+
+auto IsClassOfInterest() {
+ using namespace clang::ast_matchers;
+ using namespace clang::ast_matchers::internal;
+ static std::vector<std::string> names = Explode(classes_of_interest_option);
+ return cxxRecordDecl(Matcher<clang::NamedDecl>(new HasNameMatcher(names)));
+}
+
+auto IsFieldOfInterest() {
+ using namespace clang::ast_matchers;
+ using namespace clang::ast_matchers::internal;
+ static std::vector<std::string> names = Explode(fields_of_interest_option);
+ return fieldDecl(Matcher<clang::NamedDecl>(new HasNameMatcher(names)));
+}
+
+// Matches `object.member` and `object->member` where `member` is a
+// member-of-interest of a class-of-interest
+auto member_expr_of_interest() {
+ using namespace clang::ast_matchers;
+ return memberExpr(allOf(member(allOf(IsFieldOfInterest(),
+ fieldDecl(hasParent(cxxRecordDecl(
+ IsClassOfInterest()))))),
+ // Avoids matching memberExprs in defaulted
+ // constructors and operators.
+ unless(hasAncestor(functionDecl(isDefaulted())))))
+ .bind("member_expr");
+}
+
+// Matches `f.member = foo`.
+auto write_access() {
+ using namespace clang::ast_matchers;
+ return expr(binaryOperation(
+ hasOperatorName("="),
+ hasLHS(ignoringParenImpCasts(member_expr_of_interest())),
+ hasRHS(expr().bind("rhs"))))
+ .bind("assignment");
+}
+
+} // namespace matchers
+
+class BaseRewriter {
+ public:
+ explicit BaseRewriter(OutputHelper* output_helper)
+ : output_helper_(*output_helper) {}
+
+ protected:
+ OutputHelper& output_helper_;
+};
+
+// Replaces `object.member` with `object.member()`.
+class FieldReadAccessRewriter
+ : public BaseRewriter,
+ public clang::ast_matchers::MatchFinder::MatchCallback {
+ public:
+ using BaseRewriter::BaseRewriter;
+
+ void AddMatchers(clang::ast_matchers::MatchFinder& match_finder) {
+ using namespace clang::ast_matchers;
+ using namespace matchers;
+ // Matches `f.member` and `f->member`, except on the left-hand side of
+ // assignments.
+ auto read_access = expr(anyOf(
+ allOf(member_expr_of_interest(), unless(hasAncestor(write_access()))),
+ expr(binaryOperation(
+ hasOperatorName("="),
+ hasRHS(anyOf(member_expr_of_interest(),
+ hasDescendant(member_expr_of_interest())))))));
+ match_finder.addMatcher(read_access, this);
+ }
+
+ private:
+ void run(
+ const clang::ast_matchers::MatchFinder::MatchResult& result) override {
+ // object.member
+ // ^-----------^ "member_expr"
+ const auto* member_expr =
+ result.Nodes.getNodeAs<clang::MemberExpr>("member_expr");
+ assert(member_expr);
+ clang::CharSourceRange range = clang::CharSourceRange::getTokenRange(
+ clang::SourceRange(member_expr->getMemberLoc()));
+ auto source_text = clang::Lexer::getSourceText(
+ range, *result.SourceManager, result.Context->getLangOpts());
+ std::string replacement_text =
+ std::string(source_text.begin(), source_text.end()) + "()";
+ output_helper_.Replace(range, replacement_text, *result.SourceManager,
+ result.Context->getLangOpts());
+ }
+};
+
+// Replaces `object.member = something` with `object.set_value(something)`
+// (modulo whitespace).
+//
+// More precisely, it inserts `set_` before `member`, removes `=`, and puts
+// `something` into parentheses. We do it this way to avoid overlapping
+// substitutions because `something` might need substitutions on its own.
+class FieldWriteAccessRewriter
+ : public BaseRewriter,
+ public clang::ast_matchers::MatchFinder::MatchCallback {
+ public:
+ using BaseRewriter::BaseRewriter;
+
+ void AddMatchers(clang::ast_matchers::MatchFinder& match_finder) {
+ match_finder.addMatcher(matchers::write_access(), this);
+ }
+
+ private:
+ void run(
+ const clang::ast_matchers::MatchFinder::MatchResult& result) override {
+ // object.member = something;
+ // ^-----------^ "member_expr"
+ // ^-------^ "rhs"
+ // ^-----------------------^ "assignment"
+ const auto* assignment = result.Nodes.getNodeAs<clang::Expr>("assignment");
+ const auto* member_expr =
+ result.Nodes.getNodeAs<clang::MemberExpr>("member_expr");
+ const auto* rhs = result.Nodes.getNodeAs<clang::Expr>("rhs");
+ assert(assignment);
+ assert(member_expr);
+ assert(rhs);
+ {
+ // Replace `member` with `set_member`.
+ clang::CharSourceRange range = clang::CharSourceRange::getTokenRange(
+ clang::SourceRange(member_expr->getMemberLoc()));
+ output_helper_.Wrap(range, "set_", "", *result.SourceManager,
+ result.Context->getLangOpts());
+ }
+ {
+ // Replace `=` with ``.
+ clang::CharSourceRange range =
+ clang::CharSourceRange::getTokenRange(assignment->getExprLoc());
+ std::string replacement_text = "";
+ output_helper_.Delete(range, *result.SourceManager,
+ result.Context->getLangOpts());
+ }
+ {
+ // Replace `something` with `(something)`.
+ clang::CharSourceRange range =
+ clang::CharSourceRange::getTokenRange(rhs->getSourceRange());
+ output_helper_.Wrap(range, "(", ")", *result.SourceManager,
+ result.Context->getLangOpts());
+ }
+ }
+};
+
+// Replaces `::testing::Field(..., &ClassOfInterest::field_of_interest, ...)`
+// with `::testing::Property(..., &ClassOfInterest::field_of_interest, ...)`.
+class TestingFieldRewriter
+ : public BaseRewriter,
+ public clang::ast_matchers::MatchFinder::MatchCallback {
+ public:
+ using BaseRewriter::BaseRewriter;
+
+ void AddMatchers(clang::ast_matchers::MatchFinder& match_finder) {
+ using namespace clang::ast_matchers;
+ using namespace matchers;
+ // Matches `Field(&ClassOfInterest::field_of_interest, ...)`.
+ auto testing_field =
+ callExpr(
+ callee(functionDecl(hasName("::testing::Field"))),
+ hasAnyArgument(expr(unaryOperator(
+ hasOperatorName("&"), hasUnaryOperand(declRefExpr(to(allOf(
+ hasParent(IsClassOfInterest()),
+ fieldDecl(IsFieldOfInterest())))))))))
+ .bind("call_expr");
+ match_finder.addMatcher(testing_field, this);
+ }
+
+ private:
+ void run(
+ const clang::ast_matchers::MatchFinder::MatchResult& result) override {
+ const auto* call_expr =
+ result.Nodes.getNodeAs<clang::CallExpr>("call_expr");
+ assert(call_expr);
+ // Replace `::testing::Field` with `::testing::Property`.
+ clang::CharSourceRange range = clang::CharSourceRange::getTokenRange(
+ call_expr->getCallee()->getSourceRange());
+ auto source_text = clang::Lexer::getSourceText(
+ range, *result.SourceManager, result.Context->getLangOpts());
+ std::string replacement_text =
+ std::string(source_text.begin(), source_text.end());
+ replacement_text =
+ std::regex_replace(replacement_text, std::regex("Field"), "Property");
+ output_helper_.Replace(range, replacement_text, *result.SourceManager,
+ result.Context->getLangOpts());
+ }
+};
+
+class FieldToFunctionRewriter : public BaseRewriter {
+ public:
+ using BaseRewriter::BaseRewriter;
+
+ void AddMatchers(clang::ast_matchers::MatchFinder& match_finder) {
+ frar_.AddMatchers(match_finder);
+ fwar_.AddMatchers(match_finder);
+ tfr_.AddMatchers(match_finder);
+ }
+
+ private:
+ FieldReadAccessRewriter frar_{&output_helper_};
+ FieldWriteAccessRewriter fwar_{&output_helper_};
+ TestingFieldRewriter tfr_{&output_helper_};
+};
+
+} // namespace
+
+int main(int argc, const char* argv[]) {
+ llvm::InitializeNativeTarget();
+ llvm::InitializeNativeTargetAsmParser();
+
+ llvm::Expected<clang::tooling::CommonOptionsParser> options =
+ clang::tooling::CommonOptionsParser::create(argc, argv,
+ rewriter_category);
+ assert(static_cast<bool>(options));
+ clang::tooling::ClangTool tool(options->getCompilations(),
+ options->getSourcePathList());
+
+ OutputHelper output_helper;
+ clang::ast_matchers::MatchFinder match_finder;
+
+ FieldToFunctionRewriter ftf_rewriter(&output_helper);
+ ftf_rewriter.AddMatchers(match_finder);
+
+ std::unique_ptr<clang::tooling::FrontendActionFactory> factory =
+ clang::tooling::newFrontendActionFactory(&match_finder, &output_helper);
+ return tool.run(factory.get());
+}
diff --git a/rewrite_autofill_form_data/OWNERS b/rewrite_autofill_form_data/OWNERS
new file mode 100644
index 0000000..7cbf1a1
--- /dev/null
+++ b/rewrite_autofill_form_data/OWNERS
@@ -0,0 +1 @@
+schwering@google.com
diff --git a/rewrite_autofill_form_data/tests/field-to-function-expected.cc b/rewrite_autofill_form_data/tests/field-to-function-expected.cc
new file mode 100644
index 0000000..808609d
--- /dev/null
+++ b/rewrite_autofill_form_data/tests/field-to-function-expected.cc
@@ -0,0 +1,219 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+namespace autofill {
+
+struct FormFieldData {
+ FormFieldData();
+ FormFieldData(const FormFieldData&);
+ FormFieldData& operator=(const FormFieldData&);
+ std::u16string value;
+ int something_else;
+};
+
+// References in compiler-generated code must not be rewritten.
+FormFieldData::FormFieldData() = default;
+FormFieldData::FormFieldData(const FormFieldData&) = default;
+FormFieldData& FormFieldData::operator=(const FormFieldData&) = default;
+
+class AutofillField : public FormFieldData {};
+
+} // namespace autofill
+
+namespace testing {
+
+template <typename Class, typename FieldType>
+void Field(FieldType Class::*field, int matcher) {}
+
+template <typename Class, typename FieldType>
+void Property(FieldType Class::*field(), int matcher) {}
+
+} // namespace testing
+
+namespace base {
+
+std::u16string ASCIIToUTF16(std::string x) {
+ return u"arbitrary string";
+}
+
+} // namespace base
+
+namespace not_autofill {
+
+struct FormFieldData {
+ std::u16string value;
+ int something_else;
+};
+
+} // namespace not_autofill
+
+// Tests that a read reference `f.value` is replaced with `r.value()`.
+std::u16string FunRead() {
+ autofill::FormFieldData f;
+ return f.value();
+}
+
+// Tests that `f.value` at the left of an assignment is replaced with
+// `f.set_value(rhs)`.
+void FunWrite() {
+ ::autofill::FormFieldData f;
+ f.set_value(u"foo");
+}
+
+// Tests that a read reference `f->value` is replaced with `r->value()`.
+std::u16string FunReadPointer() {
+ autofill::FormFieldData f;
+ autofill::FormFieldData* g = &f;
+ return g->value();
+}
+
+// Tests that `f->value` at the left of an assignment is replaced with
+// `f->set_value(rhs)`.
+void FunWritePointer() {
+ autofill::FormFieldData f;
+ autofill::FormFieldData* g = &f;
+ g->set_value(u"foo");
+}
+
+// Tests that a read reference `f.value()` in a member function is replaced with
+// `f.value()`.
+class Class {
+ static const std::u16string& value(const autofill::FormFieldData& f) {
+ return f.value();
+ }
+};
+
+// Tests that a references at the left and right hand side of an assignment aer
+// replaced appropriately.
+void FunReadAndWrite() {
+ ::autofill::FormFieldData f;
+ ::autofill::FormFieldData g;
+ f.set_value(g.value());
+}
+
+// Like FunReadAndWrite() but additionally tests that a constness doesn't affect
+// the rewriting.
+void FunReadConstAndWrite() {
+ ::autofill::FormFieldData f;
+ const ::autofill::FormFieldData g;
+ f.set_value(g.value());
+}
+
+// Like FunReadConstAndWrite() but additionally tests that redundant
+// parentheses doesn't affect the rewriting.
+void FunReadConstAndWriteWithParentheses() {
+ ::autofill::FormFieldData f;
+ const ::autofill::FormFieldData g;
+ ((f).set_value)(((g).value()));
+}
+
+// Like FunReadConstAndWrite() but additionally tests that additional whitespace
+// doesn't affect the rewriting of a read reference and that the comments
+// survive the rewriting.
+void FunReadWithWhitespace() {
+ autofill::FormFieldData f;
+ std::u16string s = f // comment 1
+ . // comment 2
+ value(); // comment 3
+}
+
+// Like FunReadConstAndWrite() but additionally tests that additional
+// whitespace doesn't affect the rewriting of a write reference and that the
+// comments survive the rewriting.
+void FunWriteWithWhitespace() {
+ autofill::FormFieldData f;
+ f // comment 1
+ . // comment 2
+ set_value // comment 3
+ // comment 4
+ (u"foo");
+}
+
+// Tests whether explicit `operator=()` is rewritten. This is desirable but
+// currently not implemented.
+void FunWriteExplicitOperator() {
+ ::autofill::FormFieldData f;
+ f.value().operator=(u"foo"); // Currently not properly rewritten.
+}
+
+namespace autofill {
+
+// Tests that references are rewritten even if the object's type isn't fully
+// qualified.
+std::u16string FunReadImplicitNamespace() {
+ FormFieldData f;
+ return f.value();
+}
+
+// Tests that a reference at the left of an assignment is replaced with
+// set_value(), and that a more complex right-hand side experission is
+// preserved.
+void FunWriteCallExpr() {
+ std::string value;
+ FormFieldData field;
+ field.set_value(base::ASCIIToUTF16(value));
+}
+
+} // namespace autofill
+
+// Like FunWriteCallExpr() but with a more complex expression at the right-hand
+// side. It involves UTF-16 string literals to test that the rewriter uses the
+// correct clang::LangOptions (and not the default argument).
+void FunWriteComplexCallExpr() {
+ auto g = [](const std::u16string& s) { return s; };
+ autofill::FormFieldData f;
+ f.set_value(u"bar" + g(std::u16string(u"foo") + u"qux"));
+}
+
+// Tests that the replacement does not touch classes from other namespaces.
+std::u16string FunReadNotAutofill() {
+ ::not_autofill::FormFieldData f;
+ return f.value;
+}
+
+// Tests that write references on members of a derived class are rewritten.
+void FunWriteDerived() {
+ autofill::AutofillField f;
+ f.set_value(u"bar");
+}
+
+// Tests that read references on vector elements are rewritten.
+std::u16string FunReadVector() {
+ std::vector<autofill::FormFieldData> fields;
+ fields.emplace_back();
+ return fields[0].value();
+}
+
+// Tests that write references on vector elements are rewritten.
+void FunWriteVector() {
+ std::vector<autofill::FormFieldData> fields;
+ fields.emplace_back();
+ fields[0].set_value(u"foo");
+}
+
+// Tests that pointers to members are not rewritten.
+std::u16string FunFieldPointer() {
+ using autofill::FormFieldData;
+ FormFieldData f;
+ std::u16string FormFieldData::*ptr = &FormFieldData::value;
+ return f.*ptr;
+}
+
+// Tests that Field(&Class::member, ...) is replaced with
+// Property(&Class::member, ...).
+void FunMatcher() {
+ ::testing::Property(&autofill::FormFieldData::value, {});
+ ::testing::Field(&autofill::FormFieldData::something_else, {});
+ ::testing::Field(¬_autofill::FormFieldData::value, {});
+ ::testing::Field(¬_autofill::FormFieldData::something_else, {});
+ using ::testing::Field;
+ using ::testing::Property;
+ Property(&autofill::FormFieldData::value, {});
+ Field(&autofill::FormFieldData::something_else, {});
+ Field(¬_autofill::FormFieldData::value, {});
+ Field(¬_autofill::FormFieldData::something_else, {});
+}
diff --git a/rewrite_autofill_form_data/tests/field-to-function-original.cc b/rewrite_autofill_form_data/tests/field-to-function-original.cc
new file mode 100644
index 0000000..3e5a99e
--- /dev/null
+++ b/rewrite_autofill_form_data/tests/field-to-function-original.cc
@@ -0,0 +1,219 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+namespace autofill {
+
+struct FormFieldData {
+ FormFieldData();
+ FormFieldData(const FormFieldData&);
+ FormFieldData& operator=(const FormFieldData&);
+ std::u16string value;
+ int something_else;
+};
+
+// References in compiler-generated code must not be rewritten.
+FormFieldData::FormFieldData() = default;
+FormFieldData::FormFieldData(const FormFieldData&) = default;
+FormFieldData& FormFieldData::operator=(const FormFieldData&) = default;
+
+class AutofillField : public FormFieldData {};
+
+} // namespace autofill
+
+namespace testing {
+
+template <typename Class, typename FieldType>
+void Field(FieldType Class::*field, int matcher) {}
+
+template <typename Class, typename FieldType>
+void Property(FieldType Class::*field(), int matcher) {}
+
+} // namespace testing
+
+namespace base {
+
+std::u16string ASCIIToUTF16(std::string x) {
+ return u"arbitrary string";
+}
+
+} // namespace base
+
+namespace not_autofill {
+
+struct FormFieldData {
+ std::u16string value;
+ int something_else;
+};
+
+} // namespace not_autofill
+
+// Tests that a read reference `f.value` is replaced with `r.value()`.
+std::u16string FunRead() {
+ autofill::FormFieldData f;
+ return f.value;
+}
+
+// Tests that `f.value` at the left of an assignment is replaced with
+// `f.set_value(rhs)`.
+void FunWrite() {
+ ::autofill::FormFieldData f;
+ f.value = u"foo";
+}
+
+// Tests that a read reference `f->value` is replaced with `r->value()`.
+std::u16string FunReadPointer() {
+ autofill::FormFieldData f;
+ autofill::FormFieldData* g = &f;
+ return g->value;
+}
+
+// Tests that `f->value` at the left of an assignment is replaced with
+// `f->set_value(rhs)`.
+void FunWritePointer() {
+ autofill::FormFieldData f;
+ autofill::FormFieldData* g = &f;
+ g->value = u"foo";
+}
+
+// Tests that a read reference `f.value()` in a member function is replaced with
+// `f.value()`.
+class Class {
+ static const std::u16string& value(const autofill::FormFieldData& f) {
+ return f.value;
+ }
+};
+
+// Tests that a references at the left and right hand side of an assignment aer
+// replaced appropriately.
+void FunReadAndWrite() {
+ ::autofill::FormFieldData f;
+ ::autofill::FormFieldData g;
+ f.value = g.value;
+}
+
+// Like FunReadAndWrite() but additionally tests that a constness doesn't affect
+// the rewriting.
+void FunReadConstAndWrite() {
+ ::autofill::FormFieldData f;
+ const ::autofill::FormFieldData g;
+ f.value = g.value;
+}
+
+// Like FunReadConstAndWrite() but additionally tests that redundant
+// parentheses doesn't affect the rewriting.
+void FunReadConstAndWriteWithParentheses() {
+ ::autofill::FormFieldData f;
+ const ::autofill::FormFieldData g;
+ ((f).value) = ((g).value);
+}
+
+// Like FunReadConstAndWrite() but additionally tests that additional whitespace
+// doesn't affect the rewriting of a read reference and that the comments
+// survive the rewriting.
+void FunReadWithWhitespace() {
+ autofill::FormFieldData f;
+ std::u16string s = f // comment 1
+ . // comment 2
+ value; // comment 3
+}
+
+// Like FunReadConstAndWrite() but additionally tests that additional
+// whitespace doesn't affect the rewriting of a write reference and that the
+// comments survive the rewriting.
+void FunWriteWithWhitespace() {
+ autofill::FormFieldData f;
+ f // comment 1
+ . // comment 2
+ value // comment 3
+ = // comment 4
+ u"foo";
+}
+
+// Tests whether explicit `operator=()` is rewritten. This is desirable but
+// currently not implemented.
+void FunWriteExplicitOperator() {
+ ::autofill::FormFieldData f;
+ f.value.operator=(u"foo"); // Currently not properly rewritten.
+}
+
+namespace autofill {
+
+// Tests that references are rewritten even if the object's type isn't fully
+// qualified.
+std::u16string FunReadImplicitNamespace() {
+ FormFieldData f;
+ return f.value;
+}
+
+// Tests that a reference at the left of an assignment is replaced with
+// set_value(), and that a more complex right-hand side experission is
+// preserved.
+void FunWriteCallExpr() {
+ std::string value;
+ FormFieldData field;
+ field.value = base::ASCIIToUTF16(value);
+}
+
+} // namespace autofill
+
+// Like FunWriteCallExpr() but with a more complex expression at the right-hand
+// side. It involves UTF-16 string literals to test that the rewriter uses the
+// correct clang::LangOptions (and not the default argument).
+void FunWriteComplexCallExpr() {
+ auto g = [](const std::u16string& s) { return s; };
+ autofill::FormFieldData f;
+ f.value = u"bar" + g(std::u16string(u"foo") + u"qux");
+}
+
+// Tests that the replacement does not touch classes from other namespaces.
+std::u16string FunReadNotAutofill() {
+ ::not_autofill::FormFieldData f;
+ return f.value;
+}
+
+// Tests that write references on members of a derived class are rewritten.
+void FunWriteDerived() {
+ autofill::AutofillField f;
+ f.value = u"bar";
+}
+
+// Tests that read references on vector elements are rewritten.
+std::u16string FunReadVector() {
+ std::vector<autofill::FormFieldData> fields;
+ fields.emplace_back();
+ return fields[0].value;
+}
+
+// Tests that write references on vector elements are rewritten.
+void FunWriteVector() {
+ std::vector<autofill::FormFieldData> fields;
+ fields.emplace_back();
+ fields[0].value = u"foo";
+}
+
+// Tests that pointers to members are not rewritten.
+std::u16string FunFieldPointer() {
+ using autofill::FormFieldData;
+ FormFieldData f;
+ std::u16string FormFieldData::*ptr = &FormFieldData::value;
+ return f.*ptr;
+}
+
+// Tests that Field(&Class::member, ...) is replaced with
+// Property(&Class::member, ...).
+void FunMatcher() {
+ ::testing::Field(&autofill::FormFieldData::value, {});
+ ::testing::Field(&autofill::FormFieldData::something_else, {});
+ ::testing::Field(¬_autofill::FormFieldData::value, {});
+ ::testing::Field(¬_autofill::FormFieldData::something_else, {});
+ using ::testing::Field;
+ using ::testing::Property;
+ Field(&autofill::FormFieldData::value, {});
+ Field(&autofill::FormFieldData::something_else, {});
+ Field(¬_autofill::FormFieldData::value, {});
+ Field(¬_autofill::FormFieldData::something_else, {});
+}