Getting started

Installation

Getting the tool

The Bronto refactoring tool is currently in early access. Please contact us if you are interested in trying it out on your codebase. If you just want to play around with the API, you can skip the installation step and try it on Compiler Explorer.

Support library

You'll need the the support library, which can be found on our GitHub repository brontosource/bronto. The support library uses the 0BSD license and compiles to nothing at runtime, so it won't impact your existing licensing, binary size, or performance.

The support library is header-only and can be easily compiled with most build systems. We provide builtin support for

  • CMake 3.10.0+
  • Bazel 8.1.1+

Hello, Bronto!

These steps walk you through a simple usage of the Bronto refactoring tool, catching a potential performance issue. Specifically, we will look for places where a std::string is compared for equality with "", when the empty member function would be more efficient.

Write the following C++ program, and save it as "hello.cc".

hello.cc
#include <bronto/bronto.hpp>
#include <string>
#include <iostream>

struct Rewrite : bronto::rewrite_expr {
  BRONTO_BEFORE()
  void EqualsEmptyString(std::string s) {
    return s == "";
  }

  BRONTO_AFTER()
  void Replacement(std::string s) {
    return s.empty();
  }
};

int main(int argc, char *argv[]) {
  if (argc != 2) { return 1; }
  std::string user = argv[1];
  if (user == "") { user = "Charlotte Bronto"; }
  std::cout << "Hello, " << user << "!\n";
  return 0;
}

Run the tool

bronto-refactor edit -- clang -xc++ -c hello.cc

Reopen "hello.cc". You should see

hello.cc
#include <bronto/bronto.hpp>
#include <string>
#include <iostream>

struct Rewrite : bronto::rewrite_expr {
  BRONTO_BEFORE()
  void EqualsEmptyString(std::string s) {
    return s == "";
  }

  BRONTO_AFTER()
  void Replacement(std::string s) {
    return s.empty();
  }
};

int main(int argc, char *argv[]) {
  if (argc != 2) { return 1; }
  std::string user = argv[1];
  if (user.empty()) { user = "Charlotte Bronto"; }
  std::cout << "Hello, " << user << "!\n";
  return 0;
}

Running the tool

The bronto-refactor has two modes: edit and patch.

  • edit will apply all changes in place, modifying any relevant files.
  • patch will create a patch file with the changes.
bronto-refactor <MODE> -- "$COMPILATION_ARGUMENTS_ARRAY[@]"

Specifying refactorings

Bronto provides two overarching utilities for specifying refactorings: Inlining and pattern-matching.

Inlining functions

Inlining is the idea of replacing a reference to a symbol with its definition. For functions, this means replacing every call to the function with whatever the body of the function does. You can annotate a function with the BRONTO_INLINE() macro to tell Bronto that the annotated function should be inlined. For example, consider the following deprecation:

template <typename T>
class Container {
 public:
  T pop() { ... }
  bool empty() const { ... }
  ...
};

// Deprecated. Use the `empty` member function instead.
BRONTO_INLINE()
bool IsEmpty(const Container& c) {
  return c.empty();
}

The BRONTO_INLINE() attribute indicates that any call to IsEmpty should be replaced with the empty member function on Container. Bronto understands C++ syntax, and will produce intelligent diffs. For instance, running Bronto with the above annotations produces the following diff (removing operator* and using operator->):

 void safe_pop(const Container<std::string>* container_ptr) {
+  if (container_ptr && !IsEmpty(*container_ptr)) {
-  if (container_ptr && !container_ptr->empty()) {
     container_ptr->pop();
   }
 }

Bronto also supports annotating member functions, operators, function templates, and constructors.

Inlining types

Bronto supports inlining types with the same BRONTO_INLINE() macro applied to using declarations.

template <typename T>
using Point3d BRONTO_INLINE() = std::array<T, 3>;

Any uses of Point3d, either in a variable declaration or in another type will be properly replaced.

Expression pattern matching

Bronto can also find and replace more general patterns than simple function calls. For example, you can optimize string concatenation by avoiding unneeded conversions with the rule:

struct OperatorPlusToStrCat : bronto::rewrite_expr {
  template <std::integral T>
  BRONTO_BEFORE()
  auto operator_plus(std::string lhs, T rhs) {
    return lhs + std::to_string(rhs));
  }

  template <typename T>
  BRONTO_AFTER()
  auto use_strcat(std::string lhs, T rhs) {
    return absl::StrCat(lhs, rhs);
  }
};

To use this form of pattern matching, a struct needs three things:

  1. It must inherit from bronto::rewrite_expr
  2. A pattern to look for, defined as a member function annotated with BRONTO_BEFORE()
  3. A pattern to be used as the replacement, defined as a member function annotated with with BRONTO_BEFORE()