Merge branch 'abbrev' of github.com:google/bloaty into abbrev
diff --git a/.gitmodules b/.gitmodules
index bad512e..c0d6fa8 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,6 +4,6 @@
 [submodule "third_party/googletest"]
 	path = third_party/googletest
 	url = https://github.com/google/googletest.git
-[submodule "third_party/libFuzzer"]
-	path = third_party/libFuzzer
-	url = https://chromium.googlesource.com/chromium/llvm-project/llvm/lib/Fuzzer
+[submodule "third_party/abseil-cpp"]
+	path = third_party/abseil-cpp
+	url = https://github.com/abseil/abseil-cpp.git
diff --git a/.travis.yml b/.travis.yml
index a540b91..70af010 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,5 @@
 language: cpp
 
-sudo: required  # only required for dist: trusty
 dist: trusty
 
 compiler:
@@ -11,4 +10,4 @@
   - linux
   - osx
 
-script: make test
+script: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. && make -j4 && make test
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..e720865
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,75 @@
+cmake_minimum_required (VERSION 2.6)
+project (Bloaty)
+
+set(RE2_BUILD_TESTING OFF CACHE BOOL "enable testing for RE2" FORCE)
+add_subdirectory(third_party/re2)
+include_directories(third_party/re2)
+
+include_directories(.)
+include_directories(src)
+include_directories(third_party/abseil-cpp)
+set(CMAKE_CXX_FLAGS "-std=c++11 -W -Wall -Wno-sign-compare")
+set(CMAKE_CXX_FLAGS_DEBUG "-g")
+set(CMAKE_CXX_FLAGS_RELEASE "-O2")
+
+add_library(libbloaty
+    src/bloaty.cc
+    src/dwarf.cc
+    src/elf.cc
+    src/macho.cc
+    # Until Abseil has a proper CMake build system
+    third_party/abseil-cpp/absl/base/internal/throw_delegate.cc
+    third_party/abseil-cpp/absl/base/internal/raw_logging.cc # Grrrr...
+    third_party/abseil-cpp/absl/strings/ascii.cc
+    third_party/abseil-cpp/absl/strings/internal/memutil.cc
+    third_party/abseil-cpp/absl/strings/numbers.cc
+    third_party/abseil-cpp/absl/strings/str_cat.cc
+    third_party/abseil-cpp/absl/strings/string_view.cc
+    )
+
+add_executable(bloaty src/main.cc)
+target_link_libraries(bloaty libbloaty re2)
+
+# All of this is to add -pthread, which is required by re2 (not us).
+find_package(Threads REQUIRED)
+if(THREADS_HAVE_PTHREAD_ARG)
+  set_property(TARGET bloaty PROPERTY COMPILE_OPTIONS "-pthread")
+  set_property(TARGET bloaty PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread")
+endif()
+if(CMAKE_THREAD_LIBS_INIT)
+  target_link_libraries(bloaty "${CMAKE_THREAD_LIBS_INIT}")
+endif()
+
+enable_testing()
+
+if(BUILD_TESTING)
+  add_subdirectory(third_party/googletest)
+  include_directories(third_party/googletest/googletest/include)
+  include_directories(third_party/googletest/googlemock/include)
+
+  set(TEST_TARGETS
+      bloaty_test
+      bloaty_misc_test
+      range_map_test
+      )
+
+  foreach(target ${TEST_TARGETS})
+    add_executable(${target} tests/${target}.cc)
+    target_link_libraries(${target} libbloaty re2 gtest_main gmock "${CMAKE_THREAD_LIBS_INIT}")
+  endforeach(target)
+
+  if($ENV{LIB_FUZZING_ENGINE})
+    add_executable(fuzz_test tests/fuzz_target.cc)
+  else()
+    add_executable(fuzz_test tests/fuzz_target.cc tests/fuzz_driver.cc)
+  endif()
+  target_link_libraries(fuzz_test libbloaty re2 "${CMAKE_THREAD_LIBS_INIT}")
+
+  file(GLOB fuzz_corpus tests/testdata/fuzz_corpus/*)
+
+  add_test(NAME range_map_test COMMAND range_map_test)
+  add_test(NAME bloaty_test_x86-64 COMMAND bloaty_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/linux-x86_64)
+  add_test(NAME bloaty_test_x86 COMMAND bloaty_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/linux-x86)
+  add_test(NAME bloaty_misc_test COMMAND bloaty_misc_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/misc)
+  add_test(NAME fuzz_test COMMAND fuzz_test ${fuzz_corpus} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata/fuzz_corpus)
+endif()
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 94f6cc6..0000000
--- a/Makefile
+++ /dev/null
@@ -1,103 +0,0 @@
-
-.PHONY: clean deepclean
-
-# Disable -Wsign-compare because StringPiece currently unhelpfully defines
-# size() as ssize_t instad of size_t.
-CXXFLAGS=-std=c++11 -W -Wall -Wno-sign-compare -g -I third_party/re2 -I. -Isrc
-RE2_H=third_party/re2/re2/re2.h
-RE2_A=third_party/re2/obj/libre2.a
-
-bloaty: src/main.cc src/libbloaty.a $(RE2_A)
-	$(CXX) $(GC_SECTIONS) $(CXXFLAGS) -O2 -o $@ $^ -lpthread
-
-OBJS=src/bloaty.o src/dwarf.o src/elf.o src/macho.o
-
-$(OBJS): %.o : %.cc src/bloaty.h src/dwarf_constants.h $(RE2_H)
-	$(CXX) $(CXXFLAGS) -O2 -c -o $@ $<
-
-src/libbloaty.a: $(OBJS)
-	ar rcs $@ $^
-
-third_party/re2/obj/libre2.a: third_party/re2/Makefile
-	$(MAKE) -C third_party/re2 CPPFLAGS="-ffunction-sections -fdata-sections -g"
-
-# These targets share a pattern match to coerce make into only executing once
-# See this discussion: http://stackoverflow.com/a/3077254/1780018
-third%party/re2/Makefile third%party/re2/re2/re2.h third%party/googletest/CMakeLists.txt third%party/libFuzzer/build.sh: .gitmodules
-	git submodule init && git submodule update
-	@# Ensure .gitmodules cannot be newer
-	touch -r .gitmodules $@
-
-clean:
-	rm -f bloaty src/*.o src/*.a
-	rm -f tests/range_map_test tests/bloaty_test tests/bloaty_misc_test
-	rm -rf *.dSYM
-
-deepclean: clean
-	rm -rf third_party/re2 third_party/googletest third_party/libFuzzer
-
-## Tests #######################################################################
-
-TESTFLAGS=-Ithird_party/googletest/googletest/include -Ithird_party/googletest/googlemock/include
-TESTLIBS=src/libbloaty-test.a $(RE2_A) third_party/googletest/googlemock/gtest/libgtest_main.a third_party/googletest/googlemock/gtest/libgtest.a
-
-ifeq ($(CXX), clang++)
-  TESTFLAGS += -fsanitize=address
-endif
-
-TESTOBJS=$(OBJS:src/%.o=src/%.test.o)
-
-$(TESTOBJS): %.test.o : %.cc src/bloaty.h src/dwarf_constants.h $(RE2_H)
-	$(CXX) $(CXXFLAGS) $(TESTFLAGS) -c -o $@ $<
-
-src/libbloaty-test.a: $(TESTOBJS)
-	ar rcs $@ $^
-
-test: tests/range_map_test tests/bloaty_test tests/bloaty_misc_test
-	TOP=`pwd`; \
-	tests/range_map_test && \
-	(cd tests/testdata/linux-x86_64 && $$TOP/tests/bloaty_test) && \
-	(cd tests/testdata/linux-x86 && $$TOP/tests/bloaty_test) && \
-	(cd tests/testdata/misc && $$TOP/tests/bloaty_misc_test)
-
-tests/range_map_test: tests/range_map_test.cc $(TESTLIBS)
-	$(CXX) $(CXXFLAGS) $(TESTFLAGS) -o $@ $^ -lpthread
-
-tests/bloaty_test: tests/bloaty_test.cc $(TESTLIBS)
-	$(CXX) $(CXXFLAGS) $(TESTFLAGS) -o $@ $^ -lpthread
-
-tests/bloaty_misc_test: tests/bloaty_misc_test.cc $(TESTLIBS)
-	$(CXX) $(CXXFLAGS) $(TESTFLAGS) -o $@ $^ -lpthread
-
-third_party/googletest/googlemock/gtest/libgtest_main.a: third_party/googletest/CMakeLists.txt
-	cd third_party/googletest && cmake . && $(MAKE)
-
-## Fuzzing #####################################################################
-
-FUZZFLAGS=-fsanitize=address -fsanitize-coverage=trace-pc-guard
-TESTCMD=-fsanitize-coverage=trace-pc-guard -c -x c++ /dev/null -o /dev/null 2> /dev/null
-TESTRESULT=$(shell $(CXX) $(TESTCMD) && echo ok)
-ifeq ($(TESTRESULT), ok)
-fuzz: tests/fuzz_target
-else
-fuzz:
-	echo "Fuzzing requires that CXX is a very recent Clang (ie from svn"
-	false
-endif
-
-LIBFUZZER=third_party/libFuzzer/libFuzzer.a
-FUZZLIBS=$(BLOATYLIBS) $(LIBFUZZER)
-
-FUZZOBJS=$(OBJS:src/%.o=src/%.fuzz.o)
-
-$(FUZZOBJS): %.fuzz.o : %.cc src/bloaty.h src/dwarf_constants.h $(RE2_H)
-	$(CXX) $(CXXFLAGS) $(FUZZFLAGS) -c -o $@ $<
-
-src/libbloaty-fuzz.a: $(FUZZOBJS)
-	ar rcs $@ $^
-
-$(LIBFUZZER): third_party/libFuzzer/build.sh
-	cd third_party/libFuzzer && ./build.sh
-
-tests/fuzz_target: tests/fuzz_target.cc src/libbloaty-fuzz.a $(LIBFUZZER) $(RE2_A)
-	$(CXX) $(CXXFLAGS) $(FUZZFLAGS) -o $@ $^ -lpthread
diff --git a/src/bloaty.cc b/src/bloaty.cc
index e3d86cb..dbea25e 100644
--- a/src/bloaty.cc
+++ b/src/bloaty.cc
@@ -13,13 +13,14 @@
 // limitations under the License.
 
 #include <array>
-#include <cmath>
 #include <cinttypes>
+#include <cmath>
 #include <fstream>
 #include <iostream>
 #include <limits>
 #include <map>
 #include <memory>
+#include <queue>
 #include <set>
 #include <sstream>
 #include <string>
@@ -38,11 +39,15 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include "absl/strings/str_join.h"
+#include "absl/strings/string_view.h"
 #include "re2/re2.h"
 #include <assert.h>
 
 #include "bloaty.h"
 
+using absl::string_view;
+
 #define STRINGIFY(x) #x
 #define TOSTRING(x) STRINGIFY(x)
 #define CHECK_SYSCALL(call) \
@@ -104,6 +109,32 @@
   }
 }
 
+std::string CSVEscape(string_view str) {
+  bool need_escape = false;
+
+  for (char ch : str) {
+    if (ch == '"' || ch == ',') {
+      need_escape = true;
+      break;
+    }
+  }
+
+  if (need_escape) {
+    std::string ret = "\"";
+    for (char ch : str) {
+      if (ch == '"') {
+        ret += "\"\"";
+      } else {
+        ret += ch;
+      }
+    }
+    ret += "\"";
+    return ret;
+  } else {
+    return std::string(str);
+  }
+}
+
 template <class Func>
 class RankComparator {
  public:
@@ -117,7 +148,7 @@
 };
 
 template <class Func>
-RankComparator<Func> MakeRankComparator(Func func) {
+constexpr RankComparator<Func> MakeRankComparator(Func func) {
   return RankComparator<Func>(func);
 }
 
@@ -196,14 +227,14 @@
 class NameStripper {
  public:
   bool StripName(const std::string& name) {
-    if (name[name.size() - 1] != ')') {
-      // (anonymous namespace)::ctype_w
-      stripped_ = &name;
-      return false;
-    }
-
     int nesting = 0;
     for (size_t n = name.size() - 1; n < name.size(); --n) {
+      if (name[n] == ':' && nesting == 0) {
+        // (anonymous namespace)::ctype_w
+        stripped_ = &name;
+        return false;
+      }
+
       if (name[n] == '(') {
         if (--nesting == 0) {
           storage_ = name.substr(0, n);
@@ -345,7 +376,7 @@
   // applied in sequence.
   void AddRegex(const std::string& regex, const std::string& replacement);
 
-  std::string Munge(StringPiece name) const;
+  std::string Munge(string_view name) const;
 
  private:
   BLOATY_DISALLOW_COPY_AND_ASSIGN(NameMunger);
@@ -353,12 +384,12 @@
 };
 
 void NameMunger::AddRegex(const std::string& regex, const std::string& replacement) {
-  std::unique_ptr<RE2> re2(new RE2(StringPiece(regex)));
+  std::unique_ptr<RE2> re2(new RE2(regex));
   regexes_.push_back(std::make_pair(std::move(re2), replacement));
 }
 
-std::string NameMunger::Munge(StringPiece name) const {
-  std::string ret(name.as_string());
+std::string NameMunger::Munge(string_view name) const {
+  std::string ret(name);
 
   for (const auto& pair : regexes_) {
     if (RE2::Replace(&ret, *pair.first, pair.second)) {
@@ -673,6 +704,124 @@
 }
 
 
+// SuffixArray /////////////////////////////////////////////////////////////////
+
+// A generalized enhanced suffix array
+//   https://en.wikipedia.org/wiki/Suffix_array
+//
+// Generalized means it holds suffixes from multiple source strings.  Enhanced
+// means that in addition to the suffix array proper, it also can contain other
+// arrays such as the "lcp" array (longest common prefix) and others.
+
+class SuffixArray {
+ public:
+  size_t size() const { return array_.size(); }
+
+  // You may only call this when the object is first constructed.  Once any
+  // other methods are called it may no longer be called.
+  void AddString(string_view str) {
+    assert(array_.empty());
+    strings_.push_back(str);
+  }
+
+  std::pair<string_view, size_t> GetSuffix(size_t i) {
+    Entry e = entry(i);
+    return std::make_pair(GetStringFromEntry(e), e.str);
+  }
+
+  uint32_t GetLcp(size_t i) {
+    return lcp_[i];
+  }
+
+  void ComputeArray();
+  void ComputeLcp();
+
+ private:
+  struct Entry {
+    // This suffix is strings_[str].substr(ofs).
+    Entry(uint32_t str_, uint32_t ofs_) : str(str_), ofs(ofs_) {}
+    uint32_t str;
+    uint32_t ofs;
+  };
+
+  Entry entry(size_t i) { return array_[i]; }
+
+  string_view GetStringFromEntry(Entry entry) {
+    return strings_[entry.str].substr(entry.ofs);
+  }
+
+  std::vector<string_view> strings_;
+
+  std::vector<Entry> array_;
+  std::vector<uint32_t> lcp_;
+};
+
+void SuffixArray::ComputeArray() {
+  assert(array_.empty());
+
+  // This is a naive, O(n^2 lg n) algorithm.
+  // Several O(n) algorithms exist, but they are much, much more complicated.
+  // The fastest known algorithm is described by Nong, Zhang & Chan (2009):
+  //   Linear Suffix Array Construction by Almost Pure Induced-Sorting
+  // We can replace this with a faster algorithm if/when it is an issue.
+  for (size_t i = 0; i < strings_.size(); i++) {
+    string_view str = strings_[i];
+    for (size_t j = 0; j < str.size(); j++) {
+      char last_ch = 0;
+      if (j > 0) last_ch = str[j - 1];
+      if (j == 0 || last_ch == '/' || last_ch == '<') {
+        array_.push_back(Entry(i, j));
+      }
+    }
+  }
+
+  std::sort(array_.begin(), array_.end(), [this](Entry a, Entry b) {
+    return GetStringFromEntry(a) < GetStringFromEntry(b);
+  });
+}
+
+void SuffixArray::ComputeLcp() {
+  assert(!array_.empty());
+  assert(lcp_.empty());
+
+  // This is a naive, O(n^2) algorithm.
+  // Several O(n) algorithms exist, but they are much, much more complicated.
+  // The fastest known O(n) algorithm is described by Fischer (2011):
+  //   Inducing the LCP-Array
+  // We can replace this with a faster algorithm if/when it is an issue.
+  lcp_.resize(array_.size());
+  string_view last = GetStringFromEntry(entry(0));
+  for (size_t i = 1; i < array_.size(); i++) {
+    string_view curr = GetStringFromEntry(entry(i));
+    size_t limit = std::min(curr.size(), last.size());
+    size_t j;
+    int nesting = 0;
+    size_t nesting_start = 0;
+    for (j = 0; j < limit; j++) {
+      if (curr[j] != last[j]) {
+        break;
+      }
+      if (curr[j] == ',' && nesting == 0) {
+        break;
+      }
+      if (curr[j] == '<' || curr[j] == '(') {
+        if (nesting++ == 0) {
+          nesting_start = j;
+        }
+      }
+      if ((curr[j] == '>' || curr[j] == ')') && --nesting < 0) {
+        break;
+      }
+    }
+    if (nesting > 0) {
+      j = nesting_start;
+    }
+    lcp_[i] = j;
+    last = curr;
+  }
+}
+
+
 // RollupOutput ////////////////////////////////////////////////////////////////
 
 // RollupOutput represents rollup data after we have applied output massaging
@@ -775,8 +924,8 @@
   }
 }
 
-void RollupOutput::PrintRow(const RollupRow& row, size_t indent,
-                            std::ostream* out) const {
+void RollupOutput::PrettyPrintRow(const RollupRow& row, size_t indent,
+                                  std::ostream* out) const {
   *out << FixedWidthString("", indent) << " "
        << PercentString(row.vmpercent, row.diff_mode) << " "
        << SiPrint(row.vmsize, row.diff_mode) << " "
@@ -785,10 +934,10 @@
        << PercentString(row.filepercent, row.diff_mode) << "\n";
 }
 
-void RollupOutput::PrintTree(const RollupRow& row, size_t indent,
-                             std::ostream* out) const {
+void RollupOutput::PrettyPrintTree(const RollupRow& row, size_t indent,
+                                   std::ostream* out) const {
   // Rows are printed before their sub-rows.
-  PrintRow(row, indent, out);
+  PrettyPrintRow(row, indent, out);
 
   // For now we don't print "confounding" sub-entries.  For example, if we're
   // doing a two-level analysis "-d section,symbol", and a section is growing
@@ -799,19 +948,19 @@
 
   if (row.vmsize > 0 || row.filesize > 0) {
     for (const auto& child : row.sorted_children) {
-      PrintTree(child, indent + 4, out);
+      PrettyPrintTree(child, indent + 4, out);
     }
   }
 
   if (row.vmsize < 0 || row.filesize < 0) {
     for (const auto& child : row.shrinking) {
-      PrintTree(child, indent + 4, out);
+      PrettyPrintTree(child, indent + 4, out);
     }
   }
 
   if ((row.vmsize < 0) != (row.filesize < 0)) {
     for (const auto& child : row.mixed) {
-      PrintTree(child, indent + 4, out);
+      PrettyPrintTree(child, indent + 4, out);
     }
   }
 }
@@ -827,64 +976,69 @@
 void RollupOutput::AbbreviateToFit(size_t width) {
   std::vector<std::string*> names;
   std::string best_prefix;
-  int best_prefix_weight = 0;
   CollectNames(&toplevel_row_, &names);
+  SuffixArray suffixes;
 
-  while (1) {
-    printf("Yo!\n");
-    sleep(1);
-    size_t saw_overwide = false;
-    for (auto name : names) {
-      if (name->size() > width) {
-        saw_overwide = true;
-        break;
-      }
-    }
-    if (!saw_overwide) {
-      return;
-    }
+  for (size_t i = 0; i < names.size(); i++) {
+    suffixes.AddString(*names[i]);
+  }
 
-    size_t n = 0;
-    std::unordered_set<const std::string*> have_prefix;
-    std::copy(names.begin(), names.end(), std::inserter(have_prefix ,have_prefix.end()));
-    while (have_prefix.size() > 1) {
-      const std::string* str0 = *have_prefix.begin();
-      char ch = str0->at(n);
-      if (true /*ch == '/'*/) {
-        int weight = n * have_prefix.size();
-        if (weight > best_prefix_weight) {
-          best_prefix = str0->substr(0, n);
-          best_prefix_weight = weight;
-          printf("Try prefix: %s\n", best_prefix.c_str());
-          printf("Try prefix weight: %d\n", best_prefix_weight);
-        }
-      }
-      for (auto it = have_prefix.begin(); it != have_prefix.end();) {
-	printf("Str: %s\n", (*it)->c_str());
-        if ((*it)->at(n) != ch) {
-          it = have_prefix.erase(it); // previously this was something like m_map.erase(it++);
-        } else {
-          ++it;
-        }
-      }
-      n++;
-    }
-    printf("Best prefix: %s\n", best_prefix.c_str());
-    printf("Best prefix weight: %d\n", best_prefix_weight);
+  suffixes.ComputeArray();
+  suffixes.ComputeLcp();
 
-    // Remove the best prefix from all strings that have it.
-    std::string replace_with =
-        std::string("[") + std::to_string(abbrevs_.size()) + "]";
-    abbrevs_.push_back(best_prefix);
-    for (auto name : names) {
-      if (name->compare(0, best_prefix.length(), best_prefix) == 0) {
-        name->replace(0, best_prefix.length(), replace_with);
+  struct Abbrev {
+    Abbrev(size_t weight_, string_view str_)
+        : weight(weight_),
+          str(str_.data(),
+          str_.size()) {}
+    bool operator <(const Abbrev& b) const { return weight < b.weight; }
+    size_t weight;
+    std::string str;
+  };
+
+  std::priority_queue<Abbrev> abbrevs;
+  std::vector<size_t> start_indexes;
+
+  for (size_t i = 0; i < suffixes.size(); i++) {
+    size_t lcp = suffixes.GetLcp(i);
+    std::cout << "Suffix: " << suffixes.GetSuffix(i).first << ", lcp: " << lcp << "\n";
+    if (start_indexes.empty() || lcp > suffixes.GetLcp(start_indexes.back())) {
+      start_indexes.push_back(i);
+    }
+    while (lcp < suffixes.GetLcp(start_indexes.back())) {
+      size_t index = start_indexes.back();
+      size_t len = suffixes.GetLcp(index);
+      start_indexes.pop_back();
+      size_t weight = (i - index + 1) * len * len;
+      std::cout << "Weight: " << weight << "\n";
+      if (weight > 0) {
+        string_view substr = suffixes.GetSuffix(index).first.substr(0, len);
+        abbrevs.emplace(weight, substr);
       }
     }
   }
+
+  char ch = 'A';
+  while (!abbrevs.empty()) {
+    const std::string abbrev_text = abbrevs.top().str;
+    abbrevs.pop();
+    if (abbrev_text.size() < 30) {
+      continue;
+    }
+    std::string replacement(1, ch);
+    abbrevs_.emplace_back(replacement, abbrev_text);
+    ch++;
+    for (const auto& name : names) {
+      size_t pos;
+      while ((pos = name->find(abbrev_text)) != std::string::npos) {
+        name->replace(pos, abbrev_text.size(), replacement);
+      }
+    }
+    std::cout << "Abbrev: " << abbrevs.top().str << ", weight:" << abbrevs.top().weight << "\n";
+  }
 }
 
-void RollupOutput::Print(std::ostream* out) const {
+void RollupOutput::PrettyPrint(std::ostream* out) const {
   *out << "     VM SIZE    ";
   PrintSpaces(longest_label_, out);
   *out << "    FILE SIZE";
@@ -903,7 +1057,7 @@
   }
 
   for (const auto& child : toplevel_row_.sorted_children) {
-    PrintTree(child, 0, out);
+    PrettyPrintTree(child, 0, out);
   }
 
   if (toplevel_row_.diff_mode) {
@@ -914,7 +1068,7 @@
       *out << " --------------";
       *out << "\n";
       for (const auto& child : toplevel_row_.shrinking) {
-        PrintTree(child, 0, out);
+        PrettyPrintTree(child, 0, out);
       }
     }
 
@@ -925,7 +1079,7 @@
       *out << " +-+-+-+-+-+-+-";
       *out << "\n";
       for (const auto& child : toplevel_row_.mixed) {
-        PrintTree(child, 0, out);
+        PrettyPrintTree(child, 0, out);
       }
     }
 
@@ -934,18 +1088,70 @@
   }
 
   // The "TOTAL" row comes after all other rows.
-  PrintRow(toplevel_row_, 0, out);
+  PrettyPrintRow(toplevel_row_, 0, out);
 
   if (abbrevs_.size() > 0) {
-    *out << "\n";
-    *out << "  Abbreviations:\n";
-    int i = 0;
     for (const auto& abbrev : abbrevs_) {
-      *out << "  [" << i++ << "]: " << abbrev << "\n";
+      *out << "     " << abbrev.first << ": " << abbrev.second << "\n";
     }
   }
 }
 
+void RollupOutput::PrintRowToCSV(const RollupRow& row,
+                                 string_view parent_labels,
+                                 std::ostream* out) const {
+  if (parent_labels.size() > 0) {
+    *out << parent_labels << ",";
+  }
+
+  *out << absl::StrJoin(std::make_tuple(CSVEscape(row.name),
+                                        row.vmsize,
+                                        row.filesize),
+                        ",") << "\n";
+}
+
+void RollupOutput::PrintTreeToCSV(const RollupRow& row,
+                                  string_view parent_labels,
+                                  std::ostream* out) const {
+  if (row.sorted_children.size() > 0 ||
+      row.shrinking.size() > 0 ||
+      row.mixed.size() > 0) {
+    std::string labels;
+    if (parent_labels.size() > 0) {
+      labels = absl::StrJoin(
+          std::make_tuple(parent_labels, CSVEscape(row.name)), ",");
+    } else {
+      labels = CSVEscape(row.name);
+    }
+    for (const auto& child_row : row.sorted_children) {
+      PrintTreeToCSV(child_row, labels, out);
+    }
+    for (const auto& child_row : row.shrinking) {
+      PrintTreeToCSV(child_row, labels, out);
+    }
+    for (const auto& child_row : row.mixed) {
+      PrintTreeToCSV(child_row, labels, out);
+    }
+  } else {
+    PrintRowToCSV(row, parent_labels, out);
+  }
+}
+
+void RollupOutput::PrintToCSV(std::ostream* out) const {
+  std::vector<std::string> names(source_names_);
+  names.push_back("vmsize");
+  names.push_back("filesize");
+  *out << absl::StrJoin(names, ",") << "\n";
+  for (const auto& child_row : toplevel_row_.sorted_children) {
+    PrintTreeToCSV(child_row, "", out);
+  }
+  for (const auto& child_row : toplevel_row_.shrinking) {
+    PrintTreeToCSV(child_row, "", out);
+  }
+  for (const auto& child_row : toplevel_row_.mixed) {
+    PrintTreeToCSV(child_row, "", out);
+  }
+}
 
 // RangeMap ////////////////////////////////////////////////////////////////////
 
@@ -1210,7 +1416,7 @@
   RangeMap* vm_map() { return &vm_map_; }
 
  protected:
-  std::string ApplyNameRegexes(StringPiece name);
+  std::string ApplyNameRegexes(string_view name);
 
  private:
   BLOATY_DISALLOW_COPY_AND_ASSIGN(MemoryMap);
@@ -1221,8 +1427,8 @@
   std::unique_ptr<NameMunger> munger_;
 };
 
-std::string MemoryMap::ApplyNameRegexes(StringPiece name) {
-  return munger_ ? munger_->Munge(name) : std::string(name.as_string());
+std::string MemoryMap::ApplyNameRegexes(string_view name) {
+  return munger_ ? munger_->Munge(name) : std::string(name);
 }
 
 
@@ -1265,7 +1471,7 @@
     return false;
   }
 
-  data_.set(map, buf.st_size);
+  data_ = string_view(map, buf.st_size);
   return true;
 }
 
@@ -1300,7 +1506,7 @@
 
 RangeSink::~RangeSink() {}
 
-void RangeSink::AddFileRange(StringPiece name, uint64_t fileoff,
+void RangeSink::AddFileRange(string_view name, uint64_t fileoff,
                              uint64_t filesize) {
   if (verbose_level > 2) {
     fprintf(stderr, "[%s] AddFileRange(%.*s, %" PRIx64 ", %" PRIx64 ")\n",
@@ -1340,7 +1546,7 @@
   AddVMRange(vmaddr, vmsize, name);
 }
 
-void RangeSink::AddRange(StringPiece name, uint64_t vmaddr, uint64_t vmsize,
+void RangeSink::AddRange(string_view name, uint64_t vmaddr, uint64_t vmsize,
                          uint64_t fileoff, uint64_t filesize) {
   if (verbose_level > 2) {
     fprintf(stderr, "[%s] AddRange(%.*s, %" PRIx64 ", %" PRIx64 ", %" PRIx64
@@ -1536,7 +1742,7 @@
       return ret;
     }
 
-    void PrintMapRow(StringPiece str, uint64_t start, uint64_t end) {
+    void PrintMapRow(string_view str, uint64_t start, uint64_t end) {
       printf("[%" PRIx64 ", %" PRIx64 "] %.*s\n", start, end, (int)str.size(),
              str.data());
     }
@@ -1613,6 +1819,7 @@
 
 Options:
 
+  --csv            Output in CSV format instead of human-readable.
   -d <sources>     Comma-separated list of sources to scan.
   -n <num>         How many rows to show per level before collapsing
                    other keys into '[Other]'.  Set to '0' for unlimited.
@@ -1663,6 +1870,8 @@
         return false;
       }
       base_files = true;
+    } else if (strcmp(argv[i], "--csv") == 0) {
+      output->SetCSV(true);
     } else if (strcmp(argv[i], "-d") == 0) {
       if (!CheckNextArg(i, argc, "-d")) {
         return false;
@@ -1671,6 +1880,7 @@
       Split(argv[++i], ',', &names);
       for (const auto& name : names) {
         CHECK_RETURN(bloaty.AddDataSource(name));
+        output->AddDataSourceName(name);
       }
     } else if (strcmp(argv[i], "-r") == 0) {
       std::string source_name, regex, substitution;
diff --git a/src/bloaty.h b/src/bloaty.h
index 2611b8d..1a327aa 100644
--- a/src/bloaty.h
+++ b/src/bloaty.h
@@ -29,6 +29,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include "absl/strings/string_view.h"
 #include "re2/re2.h"
 
 #define BLOATY_DISALLOW_COPY_AND_ASSIGN(class_name) \
@@ -37,8 +38,6 @@
 
 namespace bloaty {
 
-typedef re2::StringPiece StringPiece;
-
 class MemoryMap;
 
 enum class DataSource {
@@ -56,18 +55,20 @@
   virtual ~InputFile() {}
 
   const std::string& filename() const { return filename_; }
-  StringPiece data() const { return data_; }
+  absl::string_view data() const { return data_; }
 
  private:
   BLOATY_DISALLOW_COPY_AND_ASSIGN(InputFile);
   const std::string filename_;
 
  protected:
-  StringPiece data_;
+  absl::string_view data_;
 };
 
 class InputFileFactory {
  public:
+  virtual ~InputFileFactory() {}
+
   // Returns nullptr if the file could not be opened.
   virtual std::unique_ptr<InputFile> TryOpenFile(
       const std::string& filename) const = 0;
@@ -99,18 +100,19 @@
   // If vmsize or filesize is zero, this mapping is presumed not to exist in
   // that domain.  For example, .bss mappings don't exist in the file, and
   // .debug_* mappings don't exist in memory.
-  void AddRange(StringPiece name, uint64_t vmaddr, uint64_t vmsize,
+  void AddRange(absl::string_view name, uint64_t vmaddr, uint64_t vmsize,
                 uint64_t fileoff, uint64_t filesize);
 
-  void AddRange(StringPiece name, uint64_t vmaddr, uint64_t vmsize,
-                      StringPiece file_range) {
+  void AddRange(absl::string_view name, uint64_t vmaddr, uint64_t vmsize,
+                      absl::string_view file_range) {
     AddRange(name, vmaddr, vmsize, file_range.data() - file_->data().data(),
              file_range.size());
   }
 
-  void AddFileRange(StringPiece name, uint64_t fileoff, uint64_t filesize);
+  void AddFileRange(absl::string_view name,
+                    uint64_t fileoff, uint64_t filesize);
 
-  void AddFileRange(StringPiece name, StringPiece file_range) {
+  void AddFileRange(absl::string_view name, absl::string_view file_range) {
     AddFileRange(name, file_range.data() - file_->data().data(),
                  file_range.size());
   }
@@ -168,17 +170,17 @@
 namespace dwarf {
 
 struct File {
-  StringPiece debug_info;
-  StringPiece debug_types;
-  StringPiece debug_str;
-  StringPiece debug_abbrev;
-  StringPiece debug_aranges;
-  StringPiece debug_line;
+  absl::string_view debug_info;
+  absl::string_view debug_types;
+  absl::string_view debug_str;
+  absl::string_view debug_abbrev;
+  absl::string_view debug_aranges;
+  absl::string_view debug_line;
 };
 
 }  // namespace dwarf
 
-typedef std::map<StringPiece, std::pair<uint64_t, uint64_t>> SymbolTable;
+typedef std::map<absl::string_view, std::pair<uint64_t, uint64_t>> SymbolTable;
 
 // Provided by dwarf.cc.  To use these, a module should fill in a dwarf::File
 // and then call these functions.
@@ -370,19 +372,46 @@
  public:
   RollupOutput() : toplevel_row_("TOTAL") {}
   const RollupRow& toplevel_row() { return toplevel_row_; }
-  void Print(std::ostream* out) const;
+  void PrettyPrint(std::ostream* out) const;
+  void PrintToCSV(std::ostream* out) const;
   void AbbreviateToFit(size_t width);
 
+  void Print(std::ostream* out) const {
+    if (csv_) {
+      PrintToCSV(out);
+    } else {
+      PrettyPrint(out);
+    }
+  }
+
+  void SetCSV(bool csv) { csv_ = csv; }
+
+  void AddDataSourceName(absl::string_view name) {
+    source_names_.emplace_back(std::string(name));
+  }
+
  private:
   BLOATY_DISALLOW_COPY_AND_ASSIGN(RollupOutput);
   friend class Rollup;
 
+  bool csv_ = false;
   size_t longest_label_;
+  std::vector<std::string> source_names_;
   RollupRow toplevel_row_;
-  std::vector<std::string> abbrevs_;
+  std::vector<std::pair<std::string, std::string>> abbrevs_;
 
   void PrintRow(const RollupRow& row, size_t indent, std::ostream* out) const;
   void PrintTree(const RollupRow& row, size_t indent, std::ostream* out) const;
+  void PrettyPrintRow(const RollupRow& row, size_t indent,
+                      std::ostream* out) const;
+  void PrettyPrintTree(const RollupRow& row, size_t indent,
+                       std::ostream* out) const;
+  void PrintRowToCSV(const RollupRow& row,
+                     absl::string_view parent_labels,
+                     std::ostream* out) const;
+  void PrintTreeToCSV(const RollupRow& row,
+                      absl::string_view parent_labels,
+                      std::ostream* out) const;
   static void CollectNames(RollupRow* row, std::vector<std::string*>* names);
 };
 
diff --git a/src/dwarf.cc b/src/dwarf.cc
index d1ebd7f..620234e 100644
--- a/src/dwarf.cc
+++ b/src/dwarf.cc
@@ -24,11 +24,13 @@
 #include <unordered_set>
 #include <vector>
 
+#include "absl/strings/string_view.h"
 #include "bloaty.h"
 #include "dwarf_constants.h"
 #include "re2/re2.h"
 
 using namespace dwarf2reader;
+using absl::string_view;
 
 static size_t AlignUpTo(size_t offset, size_t granularity) {
   // Granularity must be a power of two.
@@ -43,7 +45,7 @@
 }
 
 #define CHECK_RETURN(call) if (!(call)) { return false; }
-#define CHECK_RETURN_STRINGPIECE(call) if (!(call)) { return StringPiece(); }
+#define CHECK_RETURN_STRINGPIECE(call) if (!(call)) { return string_view(); }
 
 
 // Low-level Parsing Routines //////////////////////////////////////////////////
@@ -53,27 +55,27 @@
 // is layered on top of these.
 
 template <class T>
-bool ReadMemcpy(StringPiece* data, T* val) {
+bool ReadMemcpy(string_view* data, T* val) {
   CHECK_RETURN(data->size() >= sizeof(T));
   memcpy(val, data->data(), sizeof(T));
   data->remove_prefix(sizeof(T));
   return true;
 }
 
-bool ReadPiece(size_t bytes, StringPiece* data, StringPiece* val) {
+bool ReadPiece(size_t bytes, string_view* data, string_view* val) {
   CHECK_RETURN(data->size() >= bytes);
   *val = data->substr(0, bytes);
   data->remove_prefix(bytes);
   return true;
 }
 
-bool SkipBytes(size_t bytes, StringPiece* data) {
+bool SkipBytes(size_t bytes, string_view* data) {
   CHECK_RETURN(data->size() >= bytes);
   data->remove_prefix(bytes);
   return true;
 }
 
-bool ReadNullTerminated(StringPiece* data, StringPiece* val) {
+bool ReadNullTerminated(string_view* data, string_view* val) {
   const char* nullz =
       static_cast<const char*>(memchr(data->data(), '\0', data->size()));
 
@@ -91,7 +93,7 @@
 
 template <class T>
 typename std::enable_if<std::is_unsigned<T>::value, bool>::type ReadLEB128(
-    StringPiece* data, T* out) {
+    string_view* data, T* out) {
   uint64_t ret = 0;
   int shift = 0;
   int maxshift = 70;
@@ -119,7 +121,7 @@
 
 template <class T>
 typename std::enable_if<std::is_signed<T>::value, bool>::type ReadLEB128(
-    StringPiece* data, T* out) {
+    string_view* data, T* out) {
   int64_t ret = 0;
   int shift = 0;
   int maxshift = 70;
@@ -150,7 +152,7 @@
   return false;
 }
 
-bool SkipLEB128(StringPiece* data) {
+bool SkipLEB128(string_view* data) {
   size_t limit =
       std::min(static_cast<size_t>(data->size()), static_cast<size_t>(10));
   for (size_t i = 0; i < limit; i++) {
@@ -182,7 +184,7 @@
 
   // Reads a DWARF offset based on whether we are reading dwarf32 or dwarf64
   // format.
-  bool ReadDWARFOffset(StringPiece* data, uint64_t* ofs) const {
+  bool ReadDWARFOffset(string_view* data, uint64_t* ofs) const {
     if (dwarf64) {
       return ReadMemcpy(data, ofs);
     } else {
@@ -194,7 +196,7 @@
   }
 
   // Reads an address according to the expected address_size.
-  bool ReadAddress(StringPiece* data, uint64_t* addr) const {
+  bool ReadAddress(string_view* data, uint64_t* addr) const {
     if (address_size == 8) {
       return ReadMemcpy(data, addr);
     } else if (address_size == 4) {
@@ -215,7 +217,7 @@
   //
   // Stores the range for this section in |data| and all of the remaining data
   // in |next|.
-  bool ReadInitialLength(StringPiece* data, StringPiece* next) {
+  bool ReadInitialLength(string_view* data, string_view* next) {
     uint64_t len;
     uint32_t len32;
     CHECK_RETURN(ReadMemcpy(data, &len32));
@@ -253,7 +255,7 @@
  public:
   // Reads abbreviations until a terminating abbreviation is seen.  Returns
   // false if there is a parse error or a premature EOF.
-  bool ReadAbbrevs(StringPiece data);
+  bool ReadAbbrevs(string_view data);
 
   // In a DWARF abbreviation, each attribute has a name and a form.
   struct Attribute {
@@ -290,7 +292,7 @@
   std::unordered_map<uint32_t, Abbrev> abbrev_;
 };
 
-bool AbbrevTable::ReadAbbrevs(StringPiece data) {
+bool AbbrevTable::ReadAbbrevs(string_view data) {
   while (true) {
     uint32_t code;
     CHECK_RETURN(ReadLEB128(&data, &code));
@@ -347,18 +349,18 @@
 class StringTable {
  public:
   // Construct with the debug_str data from a DWARF file.
-  StringTable(StringPiece debug_str) : debug_str_(debug_str) {}
+  StringTable(string_view debug_str) : debug_str_(debug_str) {}
 
   // Read a string from the table.
-  bool ReadEntry(size_t ofs, StringPiece* val) const;
+  bool ReadEntry(size_t ofs, string_view* val) const;
 
  private:
-  StringPiece debug_str_;
+  string_view debug_str_;
 };
 
-bool StringTable::ReadEntry(size_t ofs, StringPiece* val) const {
+bool StringTable::ReadEntry(size_t ofs, string_view* val) const {
   CHECK_RETURN(ofs < debug_str_.size());
-  StringPiece str = debug_str_.substr(ofs);
+  string_view str = debug_str_.substr(ofs);
   CHECK_RETURN(ReadNullTerminated(&str, val));
   return true;
 }
@@ -370,7 +372,7 @@
 
 class AddressRanges {
  public:
-  AddressRanges(StringPiece data) : section_(data), next_unit_(data) {}
+  AddressRanges(string_view data) : section_(data), next_unit_(data) {}
 
   // Offset into .debug_info for the current compilation unit.
   uint64_t debug_info_offset() { return debug_info_offset_; }
@@ -390,9 +392,9 @@
 
  private:
   CompilationUnitSizes sizes_;
-  StringPiece section_;
-  StringPiece unit_remaining_;
-  StringPiece next_unit_;
+  string_view section_;
+  string_view unit_remaining_;
+  string_view next_unit_;
   uint64_t debug_info_offset_;
   uint64_t address_;
   uint64_t length_;
@@ -507,13 +509,13 @@
   // APIs for our friends to use to update our state.
 
   // Call to get the current read head where attributes should be parsed.
-  StringPiece ReadAttributesBegin() {
+  string_view ReadAttributesBegin() {
     assert(state_ == State::kReadyToReadAttributes);
     return remaining_;
   }
 
   // When some data has been parsed, this updates our read head.
-  bool ReadAttributesEnd(StringPiece remaining, uint64_t sibling) {
+  bool ReadAttributesEnd(string_view remaining, uint64_t sibling) {
     assert(state_ == State::kReadyToReadAttributes);
     if (remaining.data() == nullptr) {
       state_ = State::kError;
@@ -528,7 +530,7 @@
 
   // Internal APIs.
 
-  bool ReadCompilationUnitHeader(StringPiece data);
+  bool ReadCompilationUnitHeader(string_view data);
   bool ReadCode();
 
   enum class State {
@@ -546,14 +548,14 @@
   const AbbrevTable::Abbrev* current_abbrev_;
 
   // Our current read position.
-  StringPiece remaining_;
+  string_view remaining_;
   uint64_t sibling_offset_;
 
   // The read position of the next entry at each level, or size()==0 for levels
   // where we don't know (because we're not at the top-level and the previous
   // DIE didn't include DW_AT_sibling).  Length of this array indicates the
   // current depth.
-  StringPiece next_unit_;
+  string_view next_unit_;
 
   // All of the AbbrevTables we've read from .debug_abbrev, indexed by their
   // offset within .debug_abbrev.
@@ -563,7 +565,7 @@
   Section section_;
 
   // Information about the current compilation unit.
-  StringPiece unit_data_;
+  string_view unit_data_;
   CompilationUnitSizes unit_sizes_;
   AbbrevTable* unit_abbrev_;
 
@@ -584,7 +586,7 @@
 bool DIEReader::ReadCode() {
   uint32_t code;
   state_ = State::kError;
-  StringPiece data = remaining_;
+  string_view data = remaining_;
 
   CHECK_RETURN(ReadLEB128(&data, &code));
 
@@ -625,7 +627,7 @@
 }
 
 bool DIEReader::SeekToCompilationUnit(Section section, uint64_t offset) {
-  StringPiece data;
+  string_view data;
   section_ = section;
 
   if (section == Section::kDebugInfo) {
@@ -642,14 +644,14 @@
   return true;
 }
 
-bool DIEReader::ReadCompilationUnitHeader(StringPiece data) {
+bool DIEReader::ReadCompilationUnitHeader(string_view data) {
   if (data.size() == 0) {
     state_ = State::kEof;
     return false;
   }
 
-  StringPiece unit_data = data;
-  StringPiece next_unit;
+  string_view unit_data = data;
+  string_view next_unit;
   unit_sizes_.ReadInitialLength(&data, &next_unit);
 
   uint16_t version;
@@ -667,7 +669,8 @@
   // If we haven't already read abbreviations for this debug_abbrev_offset, we
   // need to do so now.
   if (unit_abbrev_->IsEmpty()) {
-    StringPiece abbrev_data = dwarf_.debug_abbrev;
+    string_view abbrev_data = dwarf_.debug_abbrev;
+    CHECK_RETURN(abbrev_data.size() >= debug_abbrev_offset);
     abbrev_data.remove_prefix(debug_abbrev_offset);
     CHECK_RETURN(unit_abbrev_->ReadAbbrevs(abbrev_data));
   }
@@ -706,13 +709,13 @@
 // is not concerned with any possible *semantic* differences between the forms.
 // For example, DW_FORM_block and DW_FORM_exprloc both represent delimited
 // sections of the input, so this code treats them identically (both map to
-// StringPiece) even though DW_FORM_exprloc carries extra semantic meaning about
+// string_view) even though DW_FORM_exprloc carries extra semantic meaning about
 // the *interpretation* of those bytes.
 
 // The type of the decoding function yielded from all GetFunctionForForm()
 // functions.  The return value indicates the data that remains after we parsed
 // our value out.  If return_value.data() == nullptr, there was an error.
-typedef StringPiece FormDecodeFunc(const DIEReader& reader, StringPiece data,
+typedef string_view FormDecodeFunc(const DIEReader& reader, string_view data,
                                    void* val);
 
 // Helper to get decoding function as a function pointer.
@@ -729,31 +732,31 @@
 template <class Derived>
 class FormReaderBase {
  public:
-  FormReaderBase(const DIEReader& reader, StringPiece data)
+  FormReaderBase(const DIEReader& reader, string_view data)
       : reader_(reader), data_(data) {}
 
-  StringPiece data() const { return data_; }
+  string_view data() const { return data_; }
 
  protected:
   const DIEReader& reader_;
-  StringPiece data_;
+  string_view data_;
 
   // Function for parsing a specific, known form.  This function compiles into
   // extremely tight/optimized code for parsing this specific form into one
   // specific C++ type.
   template <bool (Derived::*mf)()>
-  static StringPiece ReadAttr(const DIEReader& reader, StringPiece data,
+  static string_view ReadAttr(const DIEReader& reader, string_view data,
                               void* val) {
     Derived form_reader(reader, data,
                         static_cast<typename Derived::type*>(val));
-    if ((form_reader.*mf)() == false) { return StringPiece(); }
+    if ((form_reader.*mf)() == false) { return string_view(); }
     return form_reader.data();
   }
 
   // Function for parsing the "indirect" form, which only gives you the concrete
   // form when you see the data.  This compiles into a switch() statement based
   // on the form we parse.
-  static StringPiece ReadIndirect(const DIEReader& reader, StringPiece data,
+  static string_view ReadIndirect(const DIEReader& reader, string_view data,
                                   void* value) {
     uint16_t form;
     CHECK_RETURN_STRINGPIECE(ReadLEB128(&data, &form));
@@ -768,20 +771,20 @@
   }
 };
 
-// FormReader for StringPiece.  We accept the true string forms (DW_FORM_string
+// FormReader for string_view.  We accept the true string forms (DW_FORM_string
 // and DW_FORM_strp) as well as a number of other forms that contain delimited
 // string data.  We also accept the generic/opaque DW_FORM_data* types; the
-// StringPiece can store the uninterpreted data which can then be interpreted by
+// string_view can store the uninterpreted data which can then be interpreted by
 // a higher layer.
 template <>
-class FormReader<StringPiece> : public FormReaderBase<FormReader<StringPiece>> {
+class FormReader<string_view> : public FormReaderBase<FormReader<string_view>> {
  public:
   typedef FormReader ME;
   typedef FormReaderBase<ME> Base;
-  typedef StringPiece type;
+  typedef string_view type;
   using Base::data_;
 
-  FormReader(const DIEReader& reader, StringPiece data, StringPiece* val)
+  FormReader(const DIEReader& reader, string_view data, string_view* val)
       : Base(reader, data), val_(val) {}
 
   template <class Func>
@@ -821,7 +824,7 @@
   }
 
  private:
-  StringPiece* val_;
+  string_view* val_;
 
   template <size_t N>
   bool ReadFixed() {
@@ -869,7 +872,7 @@
   typedef T type;
   using Base::data_;
 
-  FormReader(const DIEReader& reader, StringPiece data, T* val)
+  FormReader(const DIEReader& reader, string_view data, T* val)
       : Base(reader, data), val_(val) {}
 
   template <class Func>
@@ -961,7 +964,7 @@
   typedef bool type;
   using Base::data_;
 
-  FormReader(const DIEReader& reader, StringPiece data, bool* val)
+  FormReader(const DIEReader& reader, string_view data, bool* val)
       : Base(reader, data), val_(val) {}
 
   template <class Func>
@@ -1004,7 +1007,7 @@
   typedef void type;
   using Base::data_;
 
-  FormReader(const DIEReader& reader, StringPiece data, void* /*val*/)
+  FormReader(const DIEReader& reader, string_view data, void* /*val*/)
       : Base(reader, data) {}
 
   template <class Func>
@@ -1143,7 +1146,7 @@
                                  CompilationUnitSizes sizes, void* data,
                                  bool* has);
 
-  StringPiece ReadAttributes(const DIEReader& reader, StringPiece data) const;
+  string_view ReadAttributes(const DIEReader& reader, string_view data) const;
 
  private:
   std::vector<AttrAction> action_list_;
@@ -1206,8 +1209,8 @@
 
 // The fast path function that reads all attributes by simply calling a list of
 // function pointers to super-specialized functions.
-StringPiece ActionBuf::ReadAttributes(const DIEReader& reader,
-                                      StringPiece data) const {
+string_view ActionBuf::ReadAttributes(const DIEReader& reader,
+                                      string_view data) const {
   for (const auto& action : action_list_) {
     assert(action.func);
     data = action.func(reader, data, action.data);
@@ -1263,7 +1266,7 @@
   // If we wanted to allow some parameters to be optional, we could support
   // having params have an optional<> type.
   bool ReadAttributes(DIEReader* reader) {
-    StringPiece data = reader->ReadAttributesBegin();
+    string_view data = reader->ReadAttributesBegin();
 
     // Clear all existing attributes.
     values_ = std::tuple<Args...>();
@@ -1393,7 +1396,7 @@
   };
 
   struct FileName {
-    StringPiece name;
+    string_view name;
     uint32_t directory_index;
     uint64_t modified_time;
     uint64_t file_size;
@@ -1402,11 +1405,30 @@
   bool SeekToOffset(uint64_t offset, uint8_t address_size);
   bool ReadLineInfo();
   const LineInfo& lineinfo() const { return info_; }
-  const FileName& filename(size_t i) const { return file_names_[i]; }
-  StringPiece include_directory(size_t i) const {
+  const FileName& filename(size_t i) const { return filenames_[i]; }
+  string_view include_directory(size_t i) const {
     return include_directories_[i];
   }
 
+  const std::string& GetExpandedFilename(size_t index) {
+    // Generate these lazily.
+    if (expanded_filenames_.empty()) {
+      expanded_filenames_.resize(filenames_.size());
+    }
+
+    auto& ret = expanded_filenames_[index];
+    if (ret.empty()) {
+      const FileName& filename = filenames_[index];
+      string_view directory = include_directories_[filename.directory_index];
+      ret = std::string(directory);
+      if (!ret.empty()) {
+        ret += "/";
+      }
+      ret += std::string(filename.name);
+    }
+    return ret;
+  }
+
  private:
   struct Params {
     uint8_t minimum_instruction_length;
@@ -1420,12 +1442,13 @@
   const File& file_;
 
   CompilationUnitSizes sizes_;
-  std::vector<StringPiece> include_directories_;
-  std::vector<FileName> file_names_;
+  std::vector<string_view> include_directories_;
+  std::vector<FileName> filenames_;
   std::vector<uint8_t> standard_opcode_lengths_;
+  std::vector<std::string> expanded_filenames_;
 
-  StringPiece program_;
-  StringPiece remaining_;
+  string_view program_;
+  string_view remaining_;
 
   // Whether we are in a "shadow" part of the bytecode program.  Sometimes parts
   // of the line info program make it into the final binary even though the
@@ -1465,7 +1488,7 @@
 
 bool LineInfoReader::SeekToOffset(uint64_t offset, uint8_t address_size) {
   CHECK_RETURN(file_.debug_line.size() > offset);
-  StringPiece data = file_.debug_line.substr(offset);
+  string_view data = file_.debug_line.substr(offset);
   program_ = data;
 
   uint16_t version;
@@ -1475,7 +1498,7 @@
   CHECK_RETURN(ReadMemcpy(&data, &version));
   CHECK_RETURN(sizes_.ReadDWARFOffset(&data, &header_length));
 
-  StringPiece program = data.substr(header_length);
+  string_view program = data.substr(header_length);
 
   CHECK_RETURN(ReadMemcpy(&data, &params_.minimum_instruction_length));
   if (version == 4) {
@@ -1498,10 +1521,10 @@
   include_directories_.clear();
 
   // Implicit current directory entry.
-  include_directories_.push_back(StringPiece());
+  include_directories_.push_back(string_view());
 
   while (true) {
-    StringPiece dir;
+    string_view dir;
     CHECK_RETURN(ReadNullTerminated(&data, &dir));
     if (dir.size() == 0) {
       break;
@@ -1510,10 +1533,11 @@
   }
 
   // Read file_names.
-  file_names_.clear();
+  filenames_.clear();
+  expanded_filenames_.clear();
 
   // Filename 0 is unused.
-  file_names_.push_back(FileName());
+  filenames_.push_back(FileName());
   while (true) {
     FileName file_name;
     CHECK_RETURN(ReadNullTerminated(&data, &file_name.name));
@@ -1523,7 +1547,7 @@
     CHECK_RETURN(ReadLEB128(&data, &file_name.directory_index));
     CHECK_RETURN(ReadLEB128(&data, &file_name.modified_time));
     CHECK_RETURN(ReadLEB128(&data, &file_name.file_size));
-    file_names_.push_back(file_name);
+    filenames_.push_back(file_name);
   }
 
   info_ = LineInfo(params_.default_is_stmt);
@@ -1542,7 +1566,7 @@
   // Final step of DW_LNE_end_sequence.
   info_.end_sequence = false;
 
-  StringPiece data = remaining_;
+  string_view data = remaining_;
 
   while (true) {
     if (data.size() == 0) {
@@ -1593,7 +1617,7 @@
               CHECK_RETURN(ReadLEB128(&data, &file_name.directory_index));
               CHECK_RETURN(ReadLEB128(&data, &file_name.modified_time));
               CHECK_RETURN(ReadLEB128(&data, &file_name.file_size));
-              file_names_.push_back(file_name);
+              filenames_.push_back(file_name);
               break;
             }
             case DW_LNE_set_discriminator:
@@ -1631,6 +1655,7 @@
         case DW_LNS_set_file: {
           uint32_t operand;
           CHECK_RETURN(ReadLEB128(&data, &operand));
+          CHECK_RETURN(operand < filenames_.size());
           info_.file = operand;
           break;
         }
@@ -1711,14 +1736,14 @@
           die_reader_.GetTag() == DW_TAG_compile_unit &&
           attr_reader_.ReadAttributes(&die_reader_) &&
           attr_reader_.HasAttribute<0>()) {
-        return attr_reader_.GetAttribute<0>().as_string();
+        return std::string(attr_reader_.GetAttribute<0>());
       } else {
         return missing_;
       }
     }
 
     dwarf::DIEReader die_reader_;
-    dwarf::FixedAttrReader<StringPiece> attr_reader_;
+    dwarf::FixedAttrReader<string_view> attr_reader_;
     std::unordered_map<uint64_t, std::string> map_;
     std::string missing_;
   } map(file);
@@ -1743,7 +1768,7 @@
 static bool ReadDWARFDebugInfo(const dwarf::File& file,
                                const SymbolTable& symtab, RangeSink* sink) {
   dwarf::DIEReader die_reader(file);
-  dwarf::FixedAttrReader<StringPiece, StringPiece, uint64_t, uint64_t>
+  dwarf::FixedAttrReader<string_view, string_view, uint64_t, uint64_t>
       attr_reader(&die_reader, {DW_AT_name, DW_AT_linkage_name, DW_AT_low_pc,
                                 DW_AT_high_pc});
 
@@ -1751,7 +1776,7 @@
 
   do {
     CHECK_RETURN(attr_reader.ReadAttributes(&die_reader));
-    std::string name = attr_reader.GetAttribute<0>().as_string();
+    std::string name = std::string(attr_reader.GetAttribute<0>());
     if (name.empty()) {
       continue;
     }
@@ -1802,6 +1827,36 @@
   }
 }
 
+static void ReadDWARFStmtList(bool include_line,
+                              dwarf::LineInfoReader* line_info_reader,
+                              RangeSink* sink) {
+  uint64_t span_startaddr = 0;
+  std::string last_source;
+
+  while (line_info_reader->ReadLineInfo()) {
+    const auto& line_info = line_info_reader->lineinfo();
+    auto addr = line_info.address;
+    auto number = line_info.line;
+    auto name =
+        line_info.end_sequence
+            ? last_source
+            : LineInfoKey(line_info_reader->GetExpandedFilename(line_info.file),
+                          number, include_line);
+    if (!span_startaddr) {
+      span_startaddr = addr;
+    } else if (line_info.end_sequence ||
+        (!last_source.empty() && name != last_source)) {
+      sink->AddVMRange(span_startaddr, addr - span_startaddr, last_source);
+      if (line_info.end_sequence) {
+        span_startaddr = 0;
+      } else {
+        span_startaddr = addr;
+      }
+    }
+    last_source = name;
+  }
+}
+
 bool ReadDWARFInlines(const dwarf::File& file, RangeSink* sink,
                       bool include_line) {
   if (!file.debug_info.size() || !file.debug_line.size()) {
@@ -1812,68 +1867,17 @@
   dwarf::DIEReader die_reader(file);
   dwarf::LineInfoReader line_info_reader(file);
   dwarf::FixedAttrReader<uint64_t> attr_reader(&die_reader, {DW_AT_stmt_list});
-  std::unordered_map<uint64_t, std::string> map_;
-  std::string missing_;
-
-  class FilenameMap {
-   public:
-    FilenameMap(const dwarf::LineInfoReader& reader) : reader_(reader) {}
-    const std::string& GetSourceFilename(size_t index) {
-      auto& ret = filenames_[index];
-      if (ret.empty()) {
-        const dwarf::LineInfoReader::FileName& filename =
-            reader_.filename(index);
-        StringPiece directory =
-            reader_.include_directory(filename.directory_index);
-        ret = directory.as_string();
-        if (!ret.empty()) {
-          ret += "/";
-        }
-        ret += filename.name.as_string();
-      }
-      return ret;
-    }
-
-   private:
-    const dwarf::LineInfoReader& reader_;
-    std::unordered_map<uint32_t, std::string> filenames_;
-  };
 
   die_reader.SeekToStart(dwarf::DIEReader::Section::kDebugInfo);
 
   while (true) {
     CHECK_RETURN(attr_reader.ReadAttributes(&die_reader));
-    FilenameMap map(line_info_reader);
 
-    if (!attr_reader.HasAttribute<0>()) {
-      continue;
-    }
-
-    CHECK_RETURN(line_info_reader.SeekToOffset(attr_reader.GetAttribute<0>(),
-                 die_reader.unit_sizes().address_size));
-    uint64_t span_startaddr = 0;
-    std::string last_source;
-
-    while (line_info_reader.ReadLineInfo()) {
-      const auto& line_info = line_info_reader.lineinfo();
-      auto addr = line_info.address;
-      auto number = line_info.line;
-      auto name = line_info.end_sequence
-                      ? last_source
-                      : LineInfoKey(map.GetSourceFilename(line_info.file),
-                                    number, include_line);
-      if (!span_startaddr) {
-        span_startaddr = addr;
-      } else if (line_info.end_sequence ||
-          (!last_source.empty() && name != last_source)) {
-        sink->AddVMRange(span_startaddr, addr - span_startaddr, last_source);
-        if (line_info.end_sequence) {
-          span_startaddr = 0;
-        } else {
-          span_startaddr = addr;
-        }
-      }
-      last_source = name;
+    if (attr_reader.HasAttribute<0>()) {
+      uint64_t offset = attr_reader.GetAttribute<0>();
+      CHECK_RETURN(line_info_reader.SeekToOffset(
+          offset, die_reader.unit_sizes().address_size));
+      ReadDWARFStmtList(include_line, &line_info_reader, sink);
     }
 
     if (!die_reader.NextCompilationUnit()) {
diff --git a/src/elf.cc b/src/elf.cc
index b8fcb3f..ec2c683 100644
--- a/src/elf.cc
+++ b/src/elf.cc
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <algorithm>
 #include <string>
 #include <iostream>
+#include "absl/strings/string_view.h"
 #include "re2/re2.h"
 #include "third_party/freebsd_elf/elf.h"
 #include "bloaty.h"
@@ -22,6 +24,7 @@
 #include <limits.h>
 #include <stdlib.h>
 
+using absl::string_view;
 
 #define CHECK_RETURN(call) if (!(call)) { return false; }
 
@@ -41,30 +44,29 @@
   T operator()(T val) { return val; }
 };
 
-bool StringPieceToSize(StringPiece str, size_t* out) {
+bool StringViewToSize(string_view str, size_t* out) {
   char *end = nullptr;
   *out = strtoul(str.data(), &end, 10);
   return end != str.data() && *out != ULONG_MAX;
 }
 
-
 // ElfFile /////////////////////////////////////////////////////////////////////
 
 // For parsing the pieces we need out of an ELF file (.o, .so, and binaries).
 
 class ElfFile {
  public:
-  ElfFile(StringPiece data) : data_(data) {
+  ElfFile(string_view data) : data_(data) {
     ok_ = Initialize();
   }
 
   bool IsOpen() { return ok_; }
 
   // Regions of the file where different headers live.
-  StringPiece entire_file() const { return data_; }
-  StringPiece header_region() const { return header_region_; }
-  StringPiece section_headers() const { return section_headers_; }
-  StringPiece segment_headers() const { return segment_headers_; }
+  string_view entire_file() const { return data_; }
+  string_view header_region() const { return header_region_; }
+  string_view section_headers() const { return section_headers_; }
+  string_view segment_headers() const { return segment_headers_; }
 
   const Elf64_Ehdr& header() const { return header_; }
   Elf64_Xword section_count() const { return section_count_; }
@@ -74,32 +76,32 @@
   class Segment {
    public:
     const Elf64_Phdr& header() const { return header_; }
-    StringPiece contents() const { return contents_; }
+    string_view contents() const { return contents_; }
 
    private:
     friend class ElfFile;
     Elf64_Phdr header_;
-    StringPiece contents_;
+    string_view contents_;
   };
 
   // Represents an ELF section (.text, .data, .bss, etc.)
   class Section {
    public:
     const Elf64_Shdr& header() const { return header_; }
-    StringPiece contents() const { return contents_; }
+    string_view contents() const { return contents_; }
 
     // If header().sh_type == SHT_STRTAB.
-    bool ReadName(Elf64_Word index, StringPiece* name) const;
+    bool ReadName(Elf64_Word index, string_view* name) const;
 
     // If header().sh_type == SHT_SYMTAB
-    Elf64_Word GetSymbolCount() const;
+    bool GetSymbolCount(Elf64_Word* count) const;
     bool ReadSymbol(Elf64_Word index, Elf64_Sym* sym) const;
 
    private:
     friend class ElfFile;
     const ElfFile* elf_;
     Elf64_Shdr header_;
-    StringPiece contents_;
+    string_view contents_;
   };
 
   bool ReadSegment(Elf64_Word index, Segment* segment) const;
@@ -113,7 +115,8 @@
 
   bool Initialize();
 
-  bool SetRegion(size_t start, size_t n, StringPiece* out) const {
+  bool SetRegion(size_t start, size_t n, string_view* out) const {
+    CHECK_RETURN(SIZE_MAX - n > start);
     CHECK_RETURN(start + n <= data_.size());
     *out = data_.substr(start, n);
     return true;
@@ -123,7 +126,7 @@
   // conversion and 32->64 bit conversion, when necessary.
   class StructReader {
    public:
-    StructReader(const ElfFile& elf, StringPiece data)
+    StructReader(const ElfFile& elf, string_view data)
         : elf_(elf), data_(data) {}
 
     template <class T32, class T64, class Munger>
@@ -137,7 +140,7 @@
 
    private:
     const ElfFile& elf_;
-    StringPiece data_;
+    string_view data_;
 
     template <class T32, class T64, class Munger>
     bool ReadFallback(size_t offset, T64* out) const;
@@ -158,13 +161,13 @@
   bool ok_;
   bool is_64bit_;
   bool is_native_endian_;
-  StringPiece data_;
+  string_view data_;
   Elf64_Ehdr header_;
   Elf64_Xword section_count_;
   Elf64_Xword section_string_index_;
-  StringPiece header_region_;
-  StringPiece section_headers_;
-  StringPiece segment_headers_;
+  string_view header_region_;
+  string_view section_headers_;
+  string_view segment_headers_;
 };
 
 // ELF uses different structure definitions for 32/64 bit files.  The sizes of
@@ -258,14 +261,14 @@
   return true;
 }
 
-bool ElfFile::Section::ReadName(Elf64_Word index, StringPiece* name) const {
+bool ElfFile::Section::ReadName(Elf64_Word index, string_view* name) const {
   assert(header().sh_type == SHT_STRTAB);
 
   if (index == SHN_UNDEF || index >= contents_.size()) {
     return false;
   }
 
-  name->set(contents_.data(), contents_.size());
+  *name = string_view(contents_.data(), contents_.size());
   name->remove_prefix(index);
 
   const char* null_pos =
@@ -279,9 +282,11 @@
   return true;
 }
 
-Elf64_Word ElfFile::Section::GetSymbolCount() const {
+bool ElfFile::Section::GetSymbolCount(Elf64_Word* count) const {
   assert(header().sh_type == SHT_SYMTAB);
-  return contents_.size() / header_.sh_entsize;
+  CHECK_RETURN(header_.sh_entsize > 0);
+  *count = contents_.size() / header_.sh_entsize;
+  return true;
 }
 
 bool ElfFile::Section::ReadSymbol(Elf64_Word index, Elf64_Sym* sym) const {
@@ -361,7 +366,7 @@
 }
 
 bool ElfFile::ReadSegment(Elf64_Word index, Segment* segment) const {
-  assert(index < header_.e_phnum);
+  CHECK_RETURN(index < header_.e_phnum);
 
   Elf64_Phdr* header = &segment->header_;
   CHECK_RETURN(ReadStruct<Elf32_Phdr>(
@@ -372,14 +377,14 @@
 }
 
 bool ElfFile::ReadSection(Elf64_Word index, Section* section) const {
-  assert(index < section_count_);
+  CHECK_RETURN(index < section_count_);
 
   Elf64_Shdr* header = &section->header_;
   CHECK_RETURN(ReadStruct<Elf32_Shdr>(
       header_.e_shoff + header_.e_shentsize * index, ShdrMunger(), header));
 
   if (header->sh_type == SHT_NOBITS) {
-    section->contents_ = StringPiece();
+    section->contents_ = string_view();
   } else {
     CHECK_RETURN(
         SetRegion(header->sh_offset, header->sh_size, &section->contents_));
@@ -401,14 +406,14 @@
 
 class ArFile {
  public:
-  ArFile(StringPiece data)
+  ArFile(string_view data)
       : magic_(data.substr(0, kMagicSize)),
-        contents_(data.substr(kMagicSize)) {}
+        contents_(data.substr(std::min<size_t>(data.size(), kMagicSize))) {}
 
-  bool IsOpen() const { return magic() == StringPiece(kMagic); }
+  bool IsOpen() const { return magic() == string_view(kMagic); }
 
-  StringPiece magic() const { return magic_; }
-  StringPiece contents() const { return contents_; }
+  string_view magic() const { return magic_; }
+  string_view contents() const { return contents_; }
 
   struct MemberFile {
     enum {
@@ -416,10 +421,10 @@
       kLongFilenameTable,  // Stores long filenames, users should ignore.
       kNormal,             // Regular data file.
     } file_type;
-    StringPiece filename;  // Only when file_type == kNormal
+    string_view filename;  // Only when file_type == kNormal
     size_t size;
-    StringPiece header;
-    StringPiece contents;
+    string_view header;
+    string_view contents;
   };
 
   class MemberReader {
@@ -429,20 +434,20 @@
     bool IsEof() const { return remaining_.size() == 0; }
 
    private:
-    bool Consume(size_t n, StringPiece* field) {
+    bool Consume(size_t n, string_view* field) {
       CHECK_RETURN(remaining_.size() >= n);
       *field = remaining_.substr(0, n);
       remaining_.remove_prefix(n);
       return true;
     }
 
-    StringPiece long_filenames_;
-    StringPiece remaining_;
+    string_view long_filenames_;
+    string_view remaining_;
   };
 
  private:
-  const StringPiece magic_;
-  const StringPiece contents_;
+  const string_view magic_;
+  const string_view contents_;
 
   static constexpr const char* kMagic = "!<arch>\n";
   static constexpr int kMagicSize = 8;
@@ -466,9 +471,9 @@
   const Header* header = reinterpret_cast<const Header*>(remaining_.data());
   CHECK_RETURN(Consume(sizeof(Header), &file->header));
 
-  StringPiece file_id(&header->file_id[0], sizeof(header->file_id));
-  StringPiece size_str(&header->size[0], sizeof(header->size));
-  CHECK_RETURN(StringPieceToSize(size_str, &file->size));
+  string_view file_id(&header->file_id[0], sizeof(header->file_id));
+  string_view size_str(&header->size[0], sizeof(header->size));
+  CHECK_RETURN(StringViewToSize(size_str, &file->size));
   CHECK_RETURN(Consume(file->size, &file->contents));
   file->file_type = MemberFile::kNormal;
 
@@ -481,7 +486,7 @@
       long_filenames_ = file->contents;
     } else if (isdigit(file_id[1])) {
       size_t offset;
-      CHECK_RETURN(StringPieceToSize(file_id.substr(1), &offset));
+      CHECK_RETURN(StringViewToSize(file_id.substr(1), &offset));
       size_t end = long_filenames_.find('/', offset);
 
       if (end == std::string::npos) {
@@ -507,14 +512,14 @@
   return true;
 }
 
-void MaybeAddFileRange(RangeSink* sink, StringPiece label, StringPiece range) {
+void MaybeAddFileRange(RangeSink* sink, string_view label, string_view range) {
   if (sink) {
     sink->AddFileRange(label, range);
   }
 }
 
 template <class Func>
-bool OnElfFile(const ElfFile& elf, StringPiece filename,
+bool OnElfFile(const ElfFile& elf, string_view filename,
                unsigned long index_base, RangeSink* sink, Func func) {
   CHECK_RETURN(func(elf, filename, index_base));
 
@@ -599,12 +604,12 @@
   }
 }
 
-static bool IsArchiveFile(StringPiece data) {
+static bool IsArchiveFile(string_view data) {
   ArFile ar(data);
   return ar.IsOpen();
 }
 
-static bool IsObjectFile(StringPiece data) {
+static bool IsObjectFile(string_view data) {
   ElfFile elf(data);
   return IsArchiveFile(data) || (elf.IsOpen() && elf.header().e_type == ET_REL);
 }
@@ -623,7 +628,7 @@
 static bool ReadELFSymbols(const InputFile& file, RangeSink* sink,
                            SymbolTable* table) {
   bool is_object = IsObjectFile(file.data());
-  return ForEachElf(file, sink, [=](const ElfFile& elf, StringPiece /*filename*/,
+  return ForEachElf(file, sink, [=](const ElfFile& elf, string_view /*filename*/,
                                     uint32_t index_base) {
     for (Elf64_Xword i = 1; i < elf.section_count(); i++) {
       ElfFile::Section section;
@@ -633,7 +638,8 @@
         continue;
       }
 
-      Elf64_Word symbol_count = section.GetSymbolCount();
+      Elf64_Word symbol_count;
+      CHECK_RETURN(section.GetSymbolCount(&symbol_count));
 
       // Find the corresponding section where the strings for the symbol table
       // can be found.
@@ -643,7 +649,7 @@
 
       for (Elf64_Word i = 1; i < symbol_count; i++) {
         Elf64_Sym sym;
-        StringPiece name;
+        string_view name;
         CHECK_RETURN(section.ReadSymbol(i, &sym));
 
         int type = ELF64_ST_TYPE(sym.st_info);
@@ -661,7 +667,7 @@
         uint64_t full_addr =
             ToVMAddr(sym.st_value, index_base + sym.st_shndx, is_object);
         if (sink) {
-          sink->AddVMRangeAllowAlias(full_addr, sym.st_size, name.as_string());
+          sink->AddVMRangeAllowAlias(full_addr, sym.st_size, std::string(name));
         }
         if (table) {
           table->insert(
@@ -684,7 +690,7 @@
   bool is_object = IsObjectFile(sink->input_file().data());
   return ForEachElf(
       sink->input_file(), sink,
-      [=](const ElfFile& elf, StringPiece filename, uint32_t index_base) {
+      [=](const ElfFile& elf, string_view filename, uint32_t index_base) {
         if (elf.section_count() == 0) {
           return true;
         }
@@ -704,7 +710,7 @@
             return true;
           }
 
-          StringPiece name;
+          string_view name;
           CHECK_RETURN(section_names.ReadName(header.sh_name, &name));
 
           auto addr = header.sh_addr;
@@ -712,12 +718,12 @@
           auto filesize = (header.sh_type == SHT_NOBITS) ? 0 : size;
           auto vmsize = (header.sh_flags & SHF_ALLOC) ? size : 0;
 
-          StringPiece contents = section.contents().substr(0, filesize);
+          string_view contents = section.contents().substr(0, filesize);
 
           uint64_t full_addr = ToVMAddr(addr, index_base + i, is_object);
 
           if (report_by == kReportByFlags) {
-            name_from_flags = name.as_string();
+            name_from_flags = std::string(name);
 
             name_from_flags = "Section [";
 
@@ -764,7 +770,7 @@
   }
 
   return ForEachElf(sink->input_file(), sink, [=](const ElfFile& elf,
-                                                  StringPiece /*filename*/,
+                                                  string_view /*filename*/,
                                                   uint32_t /*index_base*/) {
     for (Elf64_Xword i = 0; i < elf.header().e_phnum; i++) {
       ElfFile::Segment segment;
@@ -816,7 +822,7 @@
       return true;
     }
 
-    StringPiece name;
+    string_view name;
     CHECK_RETURN(section_names.ReadName(header.sh_name, &name));
 
     if (name == ".debug_aranges") {
diff --git a/tests/fuzz_driver.cc b/tests/fuzz_driver.cc
new file mode 100644
index 0000000..e25dc01
--- /dev/null
+++ b/tests/fuzz_driver.cc
@@ -0,0 +1,23 @@
+#include <cassert>
+#include <iostream>
+#include <fstream>
+#include <vector>
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int main(int argc, char **argv) {
+  for (int i = 1; i < argc; i++) {
+    std::ifstream in(argv[i]);
+    in.seekg(0, in.end);
+    size_t length = in.tellg();
+    in.seekg (0, in.beg);
+    std::cout << "Reading " << length << " bytes from " << argv[i] << std::endl;
+    // Allocate exactly length bytes so that we reliably catch buffer overflows.
+    std::vector<char> bytes(length);
+    in.read(bytes.data(), bytes.size());
+    assert(in);
+    LLVMFuzzerTestOneInput(reinterpret_cast<const uint8_t *>(bytes.data()),
+                           bytes.size());
+    std::cout << "Execution successful" << std::endl;
+  }
+}
diff --git a/tests/fuzz_target.cc b/tests/fuzz_target.cc
index ea18b7a..4b89eae 100644
--- a/tests/fuzz_target.cc
+++ b/tests/fuzz_target.cc
@@ -15,11 +15,15 @@
 #include "bloaty.h"
 #include "strarr.h"
 
+#include "absl/strings/string_view.h"
+
+using absl::string_view;
+
 namespace bloaty {
 
 class StringPieceInputFile : public InputFile {
  public:
-  StringPieceInputFile(StringPiece data)
+  StringPieceInputFile(string_view data)
       : InputFile("fake_StringPieceInputFile_file") {
     data_ = data;
   }
@@ -27,11 +31,11 @@
 
 class StringPieceInputFileFactory : public InputFileFactory {
  public:
-  StringPieceInputFileFactory(StringPiece data) : data_(data) {}
+  StringPieceInputFileFactory(string_view data) : data_(data) {}
  private:
-  StringPiece data_;
+  string_view data_;
   std::unique_ptr<InputFile> TryOpenFile(
-      const std::string& filename) const override {
+      const std::string& /* filename */) const override {
     return std::unique_ptr<InputFile>(new StringPieceInputFile(data_));
   }
 };
@@ -47,7 +51,7 @@
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
   const char *data2 = reinterpret_cast<const char*>(data);
-  bloaty::StringPieceInputFileFactory factory(bloaty::StringPiece(data2, size));
+  bloaty::StringPieceInputFileFactory factory(string_view(data2, size));
 
   // Try all of the data sources.
   RunBloaty(factory, "segments");
diff --git a/tests/strarr.h b/tests/strarr.h
index d6f49ec..b8b2214 100644
--- a/tests/strarr.h
+++ b/tests/strarr.h
@@ -16,6 +16,7 @@
 #define BLOATY_TESTS_STRARR_H_
 
 #include <memory>
+#include <vector>
 
 // For constructing arrays of strings in the slightly peculiar format
 // required by execve().
diff --git a/tests/test.h b/tests/test.h
index 5fbd5cc..bb6129a 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -106,7 +106,7 @@
     if (bloaty::BloatyMain(strings.size(), StrArr(strings).get(), factory,
                            output_.get())) {
       CheckConsistency();
-      output_->Print(&std::cerr);
+      output_->PrettyPrint(&std::cerr);
       return true;
     } else {
       std::cerr << "Bloaty returned error." << "\n";
diff --git a/tests/testdata/fuzz_corpus/0034ecacd5427aafc6b97413da2053b36de5059f b/tests/testdata/fuzz_corpus/0034ecacd5427aafc6b97413da2053b36de5059f
new file mode 100644
index 0000000..133d6fa
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/0034ecacd5427aafc6b97413da2053b36de5059f
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/0153168d08d78a4eb486cdd421b3efd6a4e12844 b/tests/testdata/fuzz_corpus/0153168d08d78a4eb486cdd421b3efd6a4e12844
new file mode 100644
index 0000000..deef34e
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/0153168d08d78a4eb486cdd421b3efd6a4e12844
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/04deff284542b1271c7ed6da11b4389342793f4d b/tests/testdata/fuzz_corpus/04deff284542b1271c7ed6da11b4389342793f4d
new file mode 100644
index 0000000..72d3413
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/04deff284542b1271c7ed6da11b4389342793f4d
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/0512fc56ee361da71476c098b91d1081e5dbc4ad b/tests/testdata/fuzz_corpus/0512fc56ee361da71476c098b91d1081e5dbc4ad
new file mode 100644
index 0000000..b2fe4f2
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/0512fc56ee361da71476c098b91d1081e5dbc4ad
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/0c7d074fcd0d6863b497f6137c6cacffc59c2ae8 b/tests/testdata/fuzz_corpus/0c7d074fcd0d6863b497f6137c6cacffc59c2ae8
new file mode 100644
index 0000000..69d27e8
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/0c7d074fcd0d6863b497f6137c6cacffc59c2ae8
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/0efb04f81a05b500031405eccae9d7e8ea0721c5 b/tests/testdata/fuzz_corpus/0efb04f81a05b500031405eccae9d7e8ea0721c5
new file mode 100644
index 0000000..4a2a97c
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/0efb04f81a05b500031405eccae9d7e8ea0721c5
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/0f6736109fcd5db53450385486c4586f884feb23 b/tests/testdata/fuzz_corpus/0f6736109fcd5db53450385486c4586f884feb23
new file mode 100644
index 0000000..089d14b
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/0f6736109fcd5db53450385486c4586f884feb23
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/110a37d69bfc8f6da2f8180e907d7d2f12da1eb2 b/tests/testdata/fuzz_corpus/110a37d69bfc8f6da2f8180e907d7d2f12da1eb2
new file mode 100644
index 0000000..8b3a6b4
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/110a37d69bfc8f6da2f8180e907d7d2f12da1eb2
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/15c502b13029920e528a2982fc1559689764aaf8 b/tests/testdata/fuzz_corpus/15c502b13029920e528a2982fc1559689764aaf8
new file mode 100644
index 0000000..f4d0e23
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/15c502b13029920e528a2982fc1559689764aaf8
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/1846aea81a4e97327d5e82c8ab9e6d4c43bffee3 b/tests/testdata/fuzz_corpus/1846aea81a4e97327d5e82c8ab9e6d4c43bffee3
new file mode 100644
index 0000000..cf85b99
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/1846aea81a4e97327d5e82c8ab9e6d4c43bffee3
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/1930321f0302e111b64e38b8456ef8473f3e71d8 b/tests/testdata/fuzz_corpus/1930321f0302e111b64e38b8456ef8473f3e71d8
new file mode 100644
index 0000000..1bcd874
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/1930321f0302e111b64e38b8456ef8473f3e71d8
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/1bfe776624349462cb1d818443af106215021470 b/tests/testdata/fuzz_corpus/1bfe776624349462cb1d818443af106215021470
new file mode 100644
index 0000000..381a6fe
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/1bfe776624349462cb1d818443af106215021470
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/270dcbc8975aaff7d869faa520be996460e6f7be b/tests/testdata/fuzz_corpus/270dcbc8975aaff7d869faa520be996460e6f7be
new file mode 100644
index 0000000..d8918ce
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/270dcbc8975aaff7d869faa520be996460e6f7be
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/2877069c49bf5773d158de6911842a58768b74c3 b/tests/testdata/fuzz_corpus/2877069c49bf5773d158de6911842a58768b74c3
new file mode 100644
index 0000000..691bb03
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/2877069c49bf5773d158de6911842a58768b74c3
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/28d7fbe0ff87b53a011656f9e3c9c3aeb2ce2018 b/tests/testdata/fuzz_corpus/28d7fbe0ff87b53a011656f9e3c9c3aeb2ce2018
new file mode 100644
index 0000000..285ace1
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/28d7fbe0ff87b53a011656f9e3c9c3aeb2ce2018
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/2f6f7647f2e81f50a3f787dda064cffe03354aa8 b/tests/testdata/fuzz_corpus/2f6f7647f2e81f50a3f787dda064cffe03354aa8
new file mode 100644
index 0000000..ab4d79c
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/2f6f7647f2e81f50a3f787dda064cffe03354aa8
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/2fd5be6e7a99d71434a756a4d59a8d44db4942bb b/tests/testdata/fuzz_corpus/2fd5be6e7a99d71434a756a4d59a8d44db4942bb
new file mode 100644
index 0000000..51969e6
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/2fd5be6e7a99d71434a756a4d59a8d44db4942bb
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/3115b1163086c5904008b9a5d17a761863910214 b/tests/testdata/fuzz_corpus/3115b1163086c5904008b9a5d17a761863910214
new file mode 100644
index 0000000..39d6185
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/3115b1163086c5904008b9a5d17a761863910214
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/327c150b2d13636bb3ea5129cb58af30675e5599 b/tests/testdata/fuzz_corpus/327c150b2d13636bb3ea5129cb58af30675e5599
new file mode 100644
index 0000000..2bf5486
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/327c150b2d13636bb3ea5129cb58af30675e5599
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/3e96523b6d0025b39ddd0a771fc9f99dd1590877 b/tests/testdata/fuzz_corpus/3e96523b6d0025b39ddd0a771fc9f99dd1590877
new file mode 100644
index 0000000..c7fb188
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/3e96523b6d0025b39ddd0a771fc9f99dd1590877
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/3f3c4745b7053aca15608204a7592bac44d690cb b/tests/testdata/fuzz_corpus/3f3c4745b7053aca15608204a7592bac44d690cb
new file mode 100644
index 0000000..b98a94b
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/3f3c4745b7053aca15608204a7592bac44d690cb
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/412f1573ff1a9675377481456d8809a850d03f1b b/tests/testdata/fuzz_corpus/412f1573ff1a9675377481456d8809a850d03f1b
new file mode 100644
index 0000000..43c14c5
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/412f1573ff1a9675377481456d8809a850d03f1b
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/42f2cd88cae45b6add339ed2c2a9074ff55d9db0 b/tests/testdata/fuzz_corpus/42f2cd88cae45b6add339ed2c2a9074ff55d9db0
new file mode 100644
index 0000000..ce0b3b8
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/42f2cd88cae45b6add339ed2c2a9074ff55d9db0
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/459ef92fc33d1d9fc6048f293bab5ddb584f94a4 b/tests/testdata/fuzz_corpus/459ef92fc33d1d9fc6048f293bab5ddb584f94a4
new file mode 100644
index 0000000..d0cb244
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/459ef92fc33d1d9fc6048f293bab5ddb584f94a4
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/53a2d35a2dfe33981111fce5c8fb6514dd9570cb b/tests/testdata/fuzz_corpus/53a2d35a2dfe33981111fce5c8fb6514dd9570cb
new file mode 100644
index 0000000..336f4cb
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/53a2d35a2dfe33981111fce5c8fb6514dd9570cb
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/57354041fcdfcc3613a0762adfd5189ca60abc80 b/tests/testdata/fuzz_corpus/57354041fcdfcc3613a0762adfd5189ca60abc80
new file mode 100644
index 0000000..a1214a7
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/57354041fcdfcc3613a0762adfd5189ca60abc80
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/5a90c59187664f79cdf1ded1a6eef6854ddd9a07 b/tests/testdata/fuzz_corpus/5a90c59187664f79cdf1ded1a6eef6854ddd9a07
new file mode 100644
index 0000000..4ed21ca
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/5a90c59187664f79cdf1ded1a6eef6854ddd9a07
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/5e8ec9cbd600dcc8f6dc5eafaf34226706378b60 b/tests/testdata/fuzz_corpus/5e8ec9cbd600dcc8f6dc5eafaf34226706378b60
new file mode 100644
index 0000000..1916a86
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/5e8ec9cbd600dcc8f6dc5eafaf34226706378b60
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/64779227c42248607f46879f9e4007e66ee68269 b/tests/testdata/fuzz_corpus/64779227c42248607f46879f9e4007e66ee68269
new file mode 100644
index 0000000..bb35594
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/64779227c42248607f46879f9e4007e66ee68269
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/6511ded4f638705a5cdd071d7e21cb4febb7234c b/tests/testdata/fuzz_corpus/6511ded4f638705a5cdd071d7e21cb4febb7234c
new file mode 100644
index 0000000..1605989
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/6511ded4f638705a5cdd071d7e21cb4febb7234c
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/66845a4bce637e02379f5dbf1b860ceb7725a96d b/tests/testdata/fuzz_corpus/66845a4bce637e02379f5dbf1b860ceb7725a96d
new file mode 100644
index 0000000..d36e4a7
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/66845a4bce637e02379f5dbf1b860ceb7725a96d
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/6d385d65872fa08e194a8b806ccfd87e49f5a554 b/tests/testdata/fuzz_corpus/6d385d65872fa08e194a8b806ccfd87e49f5a554
new file mode 100644
index 0000000..2c88bfd
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/6d385d65872fa08e194a8b806ccfd87e49f5a554
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/6d7db4d97103830cd33688f18b7c6944218b58f8 b/tests/testdata/fuzz_corpus/6d7db4d97103830cd33688f18b7c6944218b58f8
new file mode 100644
index 0000000..3a1479a
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/6d7db4d97103830cd33688f18b7c6944218b58f8
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/6f0a1ec2ebc980c9296486ad1a5b8a564549aa9a b/tests/testdata/fuzz_corpus/6f0a1ec2ebc980c9296486ad1a5b8a564549aa9a
new file mode 100644
index 0000000..dc1ce42
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/6f0a1ec2ebc980c9296486ad1a5b8a564549aa9a
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/6fa62db4fbfc54538513558d0886ff8ae74e58ed b/tests/testdata/fuzz_corpus/6fa62db4fbfc54538513558d0886ff8ae74e58ed
new file mode 100644
index 0000000..bcbdc28
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/6fa62db4fbfc54538513558d0886ff8ae74e58ed
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/7b06150aa15f8aed1abd7a93f1772b893efc150e b/tests/testdata/fuzz_corpus/7b06150aa15f8aed1abd7a93f1772b893efc150e
new file mode 100644
index 0000000..fb01003
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/7b06150aa15f8aed1abd7a93f1772b893efc150e
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/7d09e7259aa0fb3da736b98b94211f71a5e513e6 b/tests/testdata/fuzz_corpus/7d09e7259aa0fb3da736b98b94211f71a5e513e6
new file mode 100644
index 0000000..6728660
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/7d09e7259aa0fb3da736b98b94211f71a5e513e6
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/7e290e80959e9f3b045387f7ec257182cb23721d b/tests/testdata/fuzz_corpus/7e290e80959e9f3b045387f7ec257182cb23721d
new file mode 100644
index 0000000..c4a8fac
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/7e290e80959e9f3b045387f7ec257182cb23721d
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/827e96b748c33f032574b9f2b7f084920feb76ab b/tests/testdata/fuzz_corpus/827e96b748c33f032574b9f2b7f084920feb76ab
new file mode 100644
index 0000000..43ae0c8
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/827e96b748c33f032574b9f2b7f084920feb76ab
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/84675e905d3771b59fd51f606bc2a14f549aba43 b/tests/testdata/fuzz_corpus/84675e905d3771b59fd51f606bc2a14f549aba43
new file mode 100644
index 0000000..b5b75bc
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/84675e905d3771b59fd51f606bc2a14f549aba43
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/8631458a27f52b7e3cdfb06a6bde899901bfd3ac b/tests/testdata/fuzz_corpus/8631458a27f52b7e3cdfb06a6bde899901bfd3ac
new file mode 100644
index 0000000..ba36762
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/8631458a27f52b7e3cdfb06a6bde899901bfd3ac
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/86a3d4b71ee172cd476d035fb9445bcbb835d92a b/tests/testdata/fuzz_corpus/86a3d4b71ee172cd476d035fb9445bcbb835d92a
new file mode 100644
index 0000000..31c7a5a
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/86a3d4b71ee172cd476d035fb9445bcbb835d92a
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/8be73e77c819315082ac4f40acc964ddfa7be6fa b/tests/testdata/fuzz_corpus/8be73e77c819315082ac4f40acc964ddfa7be6fa
new file mode 100644
index 0000000..7bf02ff
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/8be73e77c819315082ac4f40acc964ddfa7be6fa
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/8fc314d43f2d412e20a822b5f595bf61005342a9 b/tests/testdata/fuzz_corpus/8fc314d43f2d412e20a822b5f595bf61005342a9
new file mode 100644
index 0000000..e35caef
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/8fc314d43f2d412e20a822b5f595bf61005342a9
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/91acbe9b1ef167d88e8a57f16db2aa740865accd b/tests/testdata/fuzz_corpus/91acbe9b1ef167d88e8a57f16db2aa740865accd
new file mode 100644
index 0000000..9cc9dfa
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/91acbe9b1ef167d88e8a57f16db2aa740865accd
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/9b5a5fa4a46bcca17df149785daf9cd14f1c0443 b/tests/testdata/fuzz_corpus/9b5a5fa4a46bcca17df149785daf9cd14f1c0443
new file mode 100644
index 0000000..7415c4c
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/9b5a5fa4a46bcca17df149785daf9cd14f1c0443
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/9e079b888e5d223ef0bebf13ce1e26ebdd82752a b/tests/testdata/fuzz_corpus/9e079b888e5d223ef0bebf13ce1e26ebdd82752a
new file mode 100644
index 0000000..03c73f7
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/9e079b888e5d223ef0bebf13ce1e26ebdd82752a
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/a22fdce1317617bf89f3283cbd44ef490a57b5e2 b/tests/testdata/fuzz_corpus/a22fdce1317617bf89f3283cbd44ef490a57b5e2
new file mode 100644
index 0000000..54973d6
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/a22fdce1317617bf89f3283cbd44ef490a57b5e2
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/a4d1a2b246e0a1f133774daa28328c0d7ce5c3e5 b/tests/testdata/fuzz_corpus/a4d1a2b246e0a1f133774daa28328c0d7ce5c3e5
new file mode 100644
index 0000000..b70f241
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/a4d1a2b246e0a1f133774daa28328c0d7ce5c3e5
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/af0df3abd6ff306ca9161b6f6ebc96f21c6dfa98 b/tests/testdata/fuzz_corpus/af0df3abd6ff306ca9161b6f6ebc96f21c6dfa98
new file mode 100644
index 0000000..c1198fe
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/af0df3abd6ff306ca9161b6f6ebc96f21c6dfa98
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/b3a904cebb1d3070ca96cf70ec0b9ef5d1612a45 b/tests/testdata/fuzz_corpus/b3a904cebb1d3070ca96cf70ec0b9ef5d1612a45
new file mode 100644
index 0000000..b511d55
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/b3a904cebb1d3070ca96cf70ec0b9ef5d1612a45
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/bf2cb5de1de6ca492f159dc3cce67cf88a6244aa b/tests/testdata/fuzz_corpus/bf2cb5de1de6ca492f159dc3cce67cf88a6244aa
new file mode 100644
index 0000000..8c8338b
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/bf2cb5de1de6ca492f159dc3cce67cf88a6244aa
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/c121e995dd4575473e468801b301da0f219f5de7 b/tests/testdata/fuzz_corpus/c121e995dd4575473e468801b301da0f219f5de7
new file mode 100644
index 0000000..1241e8c
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/c121e995dd4575473e468801b301da0f219f5de7
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/c561ab1d99f16a04898518914dd1cea4afa7e358 b/tests/testdata/fuzz_corpus/c561ab1d99f16a04898518914dd1cea4afa7e358
new file mode 100644
index 0000000..e15376e
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/c561ab1d99f16a04898518914dd1cea4afa7e358
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/c98c037db24035a40d40f91084a56f470bb6fbc5 b/tests/testdata/fuzz_corpus/c98c037db24035a40d40f91084a56f470bb6fbc5
new file mode 100644
index 0000000..82dfe42
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/c98c037db24035a40d40f91084a56f470bb6fbc5
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/cd838035892825e361fe0f936f93fa62aaf2fab1 b/tests/testdata/fuzz_corpus/cd838035892825e361fe0f936f93fa62aaf2fab1
new file mode 100644
index 0000000..67a809c
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/cd838035892825e361fe0f936f93fa62aaf2fab1
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/d3cc4e4dddf87cb0d41135b7a22d03fc4ec11bbc b/tests/testdata/fuzz_corpus/d3cc4e4dddf87cb0d41135b7a22d03fc4ec11bbc
new file mode 100644
index 0000000..2b9411a
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/d3cc4e4dddf87cb0d41135b7a22d03fc4ec11bbc
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/daebef8f49a59b71cf57d4771b09f9f8545b83d8 b/tests/testdata/fuzz_corpus/daebef8f49a59b71cf57d4771b09f9f8545b83d8
new file mode 100644
index 0000000..5669340
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/daebef8f49a59b71cf57d4771b09f9f8545b83d8
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/dda6875f2313476f402e9a283ecaf2b50cfae316 b/tests/testdata/fuzz_corpus/dda6875f2313476f402e9a283ecaf2b50cfae316
new file mode 100644
index 0000000..bd7cfad
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/dda6875f2313476f402e9a283ecaf2b50cfae316
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/df7a639969efbe5943b6a7fa5eff4f732a50a4f6 b/tests/testdata/fuzz_corpus/df7a639969efbe5943b6a7fa5eff4f732a50a4f6
new file mode 100644
index 0000000..85ae79d
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/df7a639969efbe5943b6a7fa5eff4f732a50a4f6
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/e08b7c26f946f4761f2cecdc81f4e5e7651db9a7 b/tests/testdata/fuzz_corpus/e08b7c26f946f4761f2cecdc81f4e5e7651db9a7
new file mode 100644
index 0000000..a7799b8
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/e08b7c26f946f4761f2cecdc81f4e5e7651db9a7
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/e33d3e649729bea900f870b0cd5335f312d9ed42 b/tests/testdata/fuzz_corpus/e33d3e649729bea900f870b0cd5335f312d9ed42
new file mode 100644
index 0000000..47deab1
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/e33d3e649729bea900f870b0cd5335f312d9ed42
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/e4a3653bac41c8f39cc625286daa813e0ce603b0 b/tests/testdata/fuzz_corpus/e4a3653bac41c8f39cc625286daa813e0ce603b0
new file mode 100644
index 0000000..1844628
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/e4a3653bac41c8f39cc625286daa813e0ce603b0
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/f0ac70f708130bb9cc4aba5ebe1a3c500c4ea11a b/tests/testdata/fuzz_corpus/f0ac70f708130bb9cc4aba5ebe1a3c500c4ea11a
new file mode 100644
index 0000000..ffb7c42
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/f0ac70f708130bb9cc4aba5ebe1a3c500c4ea11a
Binary files differ
diff --git a/tests/testdata/fuzz_corpus/fc88742708813d5dd57936fad4c6e9bd6ed125ac b/tests/testdata/fuzz_corpus/fc88742708813d5dd57936fad4c6e9bd6ed125ac
new file mode 100644
index 0000000..2ba4a8e
--- /dev/null
+++ b/tests/testdata/fuzz_corpus/fc88742708813d5dd57936fad4c6e9bd6ed125ac
Binary files differ
diff --git a/third_party/abseil-cpp b/third_party/abseil-cpp
new file mode 160000
index 0000000..cc4bed2
--- /dev/null
+++ b/third_party/abseil-cpp
@@ -0,0 +1 @@
+Subproject commit cc4bed2d74f7c8717e31f9579214ab52a9c9c610
diff --git a/third_party/googletest b/third_party/googletest
index a2b8a8e..7b6561c 160000
--- a/third_party/googletest
+++ b/third_party/googletest
@@ -1 +1 @@
-Subproject commit a2b8a8e07628e5fd60644b6dd99c1b5e7d7f1f47
+Subproject commit 7b6561c56e353100aca8458d7bc49c4e0119bae8
diff --git a/third_party/libFuzzer b/third_party/libFuzzer
deleted file mode 160000
index 1b543d6..0000000
--- a/third_party/libFuzzer
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 1b543d6e5073b56be214394890c9193979a3d7e1
diff --git a/third_party/re2 b/third_party/re2
index c964d9b..16dd885 160000
--- a/third_party/re2
+++ b/third_party/re2
@@ -1 +1 @@
-Subproject commit c964d9b07816f9b52ca692e23b8b3dba33beec54
+Subproject commit 16dd8856b79b3c6163a5b6da40aa45267031a79d