Merge branch 'master' into better-tests
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..c7873ee
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,64 @@
+name: build
+
+on:
+  pull_request:
+    branches:
+      - master
+  workflow_dispatch:
+
+jobs:
+  windows:
+    runs-on: windows-latest
+
+    strategy:
+      fail-fast: false
+      matrix:
+        arch: ["Win32", "x64"] # "ARM64"
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: configure
+        run: cmake -B build -D CMAKE_BUILD_TYPE=Debug -G "Visual Studio 16 2019" -A ${{ matrix.arch }} -S .
+      - name: build
+        run: cmake --build build --config Debug
+      - name: test
+        run: ctest build
+
+  macOS:
+    runs-on: macos-latest
+
+    strategy:
+      matrix:
+        include:
+          - { cflags: "", cxxflags: "" }
+          - { cflags: "-fmodules", cxxflags: "-fmodules" }
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: configure
+        run: cmake -B build -D CMAKE_BUILD_TYPE=Debug -D CMAKE_C_FLAGS=${{ matrix.cflags }} -D CMAKE_CXX_FLAGS=${{ matrix.cxxflags }} -S .
+      - name: build
+        run: cmake --build build --config Debug
+      - name: test
+        run: ctest build
+
+  ubuntu:
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        include:
+          - { CC: gcc, CXX: g++ }
+          - { CC: clang, CXX: clang++ }
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: configure
+        run: cmake -B build -D CMAKE_BUILD_TYPE=Debug -S .
+        env:
+          CC: ${{ matrix.CC }}
+          CXX: ${{ matrix.CXX }}
+      - name: build
+        run: cmake --build build --config Debug
+      - name: test
+        run: ctest build
diff --git a/.gitmodules b/.gitmodules
index 11b244e..712299f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -9,7 +9,7 @@
 	url = https://github.com/abseil/abseil-cpp.git
 [submodule "third_party/protobuf"]
 	path = third_party/protobuf
-	url = https://github.com/google/protobuf.git
+	url = https://github.com/protocolbuffers/protobuf.git
 [submodule "third_party/capstone"]
 	path = third_party/capstone
 	url = https://github.com/aquynh/capstone.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 868d557..58f5b79 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,8 @@
 cmake_minimum_required(VERSION 3.5)
 cmake_policy(SET CMP0048 NEW)
+if(POLICY CMP0091)
+  cmake_policy(SET CMP0091 NEW)
+endif()
 project (Bloaty VERSION 1.1)
 include(CTest)
 set(CMAKE_CXX_STANDARD 17)
@@ -67,17 +70,21 @@
 
 # Set MSVC runtime before including thirdparty libraries
 if(MSVC)
-  # Link also the runtime library statically so that MSVCR*.DLL is not required at runtime.
-  # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx
-  # This is achieved by replacing msvc option /MD with /MT and /MDd with /MTd
-  # http://www.cmake.org/Wiki/CMake_FAQ#How_can_I_build_my_MSVC_application_with_a_static_runtime.3F
-  foreach(flag_var
-      CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
-      CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
-    if (flag_var MATCHES "/MD")
-      string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
-    endif()
-  endforeach()
+  if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.15)
+    set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$<CONFIG:Debug>:Debug>)
+  else()
+    # Link also the runtime library statically so that MSVCR*.DLL is not required at runtime.
+    # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx
+    # This is achieved by replacing msvc option /MD with /MT and /MDd with /MTd
+    # http://www.cmake.org/Wiki/CMake_FAQ#How_can_I_build_my_MSVC_application_with_a_static_runtime.3F
+    foreach(flag_var
+        CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
+        CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
+      if (flag_var MATCHES "/MD")
+        string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+      endif()
+    endforeach()
+  endif()
 endif()
 
 set(THREADS_PREFER_PTHREAD_FLAG TRUE)
diff --git a/src/dwarf.cc b/src/dwarf.cc
index dad4317..b1d0bec 100644
--- a/src/dwarf.cc
+++ b/src/dwarf.cc
@@ -596,6 +596,8 @@
   uint64_t debug_abbrev_offset_;
   std::string unit_name_;
   string_view unit_range_;
+  UnitType unit_type_;
+  uint64_t dwo_id_;
   CompilationUnitSizes unit_sizes_;
   AbbrevTable* unit_abbrev_;
 
@@ -605,7 +607,7 @@
   // both the current abbrev. table and the sizes.
   uint32_t abbrev_version_;
 
-  std::map<std::pair<AbbrevTable*, CompilationUnitSizes>, uint32_t>
+  std::map<std::pair<AbbrevTable*, CompilationUnitSizes>, size_t>
       abbrev_versions_;
 
   // Only for .debug_types
@@ -885,10 +887,9 @@
       return AttrValue(ReadBytes(4, data));
     case DW_FORM_data8:
       return AttrValue(ReadBytes(8, data));
-    case DW_FORM_rnglistx: {
-      auto val = AttrValue(ReadLEB128<uint64_t>(data));
-      return val;
-    }
+    case DW_FORM_loclistx:
+    case DW_FORM_rnglistx:
+      return AttrValue(ReadLEB128<uint64_t>(data));
 
     // Bloaty doesn't currently care about any bool or signed data.
     // So we fudge it a bit and just stuff these in a uint64.
@@ -977,10 +978,34 @@
   }
 
   if (unit_sizes_.dwarf_version() == 5) {
-    uint8_t unit_type = ReadFixed<uint8_t>(&remaining_);
-    (void)unit_type;  // We don't use this currently.
+    unit_type_ = static_cast<UnitType>(ReadFixed<uint8_t>(&remaining_));
     unit_sizes_.SetAddressSize(ReadFixed<uint8_t>(&remaining_));
     debug_abbrev_offset_ = unit_sizes_.ReadDWARFOffset(&remaining_);
+    switch (unit_type_) {
+    case DW_UT_skeleton:
+    case DW_UT_split_compile:
+    case DW_UT_split_type:
+      dwo_id_ = ReadFixed<uint64_t>(&remaining_);
+      break;
+    case DW_UT_type:
+      unit_type_signature_ = ReadFixed<uint64_t>(&remaining_);
+      unit_type_offset_ = unit_sizes_.ReadDWARFOffset(&remaining_);
+      break;
+    case DW_UT_compile:
+    case DW_UT_partial:
+      break;
+#if defined(_GNUC)
+    case DW_UT_lo_user ... DW_UT_hi_user:
+#else
+    case DW_UT_lo_user:
+    case DW_UT_hi_user:
+#endif
+      // User defined unit types which we do not really know about ...
+      if (verbose_level > 0) {
+        fprintf(stderr, "Unknown DWARF Unit Type in user defined range\n");
+      }
+      break;
+    }
   } else {
     debug_abbrev_offset_ = unit_sizes_.ReadDWARFOffset(&remaining_);
     unit_sizes_.SetAddressSize(ReadFixed<uint8_t>(&remaining_));
@@ -1636,15 +1661,18 @@
   // Sometimes a location is given as an offset into debug_loc.
   if (die.location && die.location->IsUint()) {
     uint64_t location = die.location->GetUint(die_reader);
-    if (location < file.debug_loc.size()) {
-      absl::string_view loc_range = file.debug_loc.substr(location);
-      loc_range = GetLocationListRange(die_reader.unit_sizes(), loc_range);
-      sink->AddFileRange("dwarf_locrange", name, loc_range);
-    } else if (verbose_level > 0) {
-      fprintf(stderr,
-              "bloaty: warning: DWARF location out of range, location=%" PRIx64
-              "\n",
-              location);
+    if (die.location->form() == DW_FORM_sec_offset) {
+      if (location < file.debug_loc.size()) {
+        absl::string_view loc_range = file.debug_loc.substr(location);
+        loc_range = GetLocationListRange(die_reader.unit_sizes(), loc_range);
+        sink->AddFileRange("dwarf_locrange", name, loc_range);
+      } else if (verbose_level > 0) {
+        fprintf(
+            stderr,
+            "bloaty: warning: DWARF location out of range, location=%" PRIx64
+            "\n",
+            location);
+      }
     }
   }
 
@@ -1653,8 +1681,10 @@
   if (die.ranges && die.ranges->form() == DW_FORM_rnglistx && die.ranges->IsUint()) {
     uint64_t range_list = die.ranges->GetUint(die_reader);
     const dwarf::CompilationUnitSizes& sizes = die_reader.unit_sizes();
+    size_t offset_size = die_reader.unit_sizes().dwarf64() ? 8 : 4;
     string_view offset_data = StrictSubstr(
-        file.debug_rnglists, die_reader.unit_sizes().range_lists_base() + range_list);
+        file.debug_rnglists, die_reader.unit_sizes().range_lists_base() +
+                                 (range_list * offset_size));
     uint64_t offset = die_reader.unit_sizes().ReadDWARFOffset(&offset_data);
     string_view data = StrictSubstr(
         file.debug_rnglists, die_reader.unit_sizes().range_lists_base() + offset);
diff --git a/src/dwarf_constants.h b/src/dwarf_constants.h
index 78891fb..7e6fc04 100644
--- a/src/dwarf_constants.h
+++ b/src/dwarf_constants.h
@@ -704,5 +704,16 @@
   DW_RLE_start_length = 0x07,
 };
 
+enum UnitType {
+  DW_UT_compile = 0x01,
+  DW_UT_type = 0x02,
+  DW_UT_partial = 0x03,
+  DW_UT_skeleton = 0x04,
+  DW_UT_split_compile = 0x05,
+  DW_UT_split_type = 0x06,
+  DW_UT_lo_user = 0x80,
+  DW_UT_hi_user = 0xff,
+};
+
 }  // namespace dwarf2reader
 #endif  // UTIL_DEBUGINFO_DWARF2ENUMS_H__
diff --git a/src/elf.cc b/src/elf.cc
index 499317d..355210b 100644
--- a/src/elf.cc
+++ b/src/elf.cc
@@ -777,60 +777,70 @@
   }
 }
 
-static void ElfMachineToCapstone(Elf64_Half e_machine, cs_arch* arch,
+static bool ElfMachineToCapstone(Elf64_Half e_machine, cs_arch* arch,
                                  cs_mode* mode) {
   switch (e_machine) {
     case EM_386:
       *arch = CS_ARCH_X86;
       *mode = CS_MODE_32;
-      break;
+      return true;
     case EM_X86_64:
       *arch = CS_ARCH_X86;
       *mode = CS_MODE_64;
-      break;
+      return true;
 
     // These aren't tested, but we include them on the off-chance
     // that it will work.
     case EM_ARM:
       *arch = CS_ARCH_ARM;
       *mode = CS_MODE_LITTLE_ENDIAN;
-      break;
+      return true;
     case EM_AARCH64:
       *arch = CS_ARCH_ARM64;
       *mode = CS_MODE_ARM;
-      break;
+      return true;
     case EM_MIPS:
       *arch = CS_ARCH_MIPS;
-      break;
+      return true;
     case EM_PPC:
       *arch = CS_ARCH_PPC;
       *mode = CS_MODE_32;
-      break;
+      return true;
     case EM_PPC64:
       *arch = CS_ARCH_PPC;
       *mode = CS_MODE_64;
-      break;
+      return true;
     case EM_SPARC:
       *arch = CS_ARCH_SPARC;
       *mode = CS_MODE_BIG_ENDIAN;
-      break;
+      return true;
     case EM_SPARCV9:
       *arch = CS_ARCH_SPARC;
       *mode = CS_MODE_V9;
-      break;
+      return true;
+
     default:
-      THROWF("Unknown ELF machine value: $0'", e_machine);
+      if (verbose_level > 1) {
+        printf(
+            "Unable to map to capstone target, disassembly will be "
+            "unavailable");
+      }
+      return false;
   }
 }
 
-static void ReadElfArchMode(const InputFile& file, cs_arch* arch, cs_mode* mode) {
+static bool ReadElfArchMode(const InputFile& file, cs_arch* arch, cs_mode* mode) {
+  bool capstone_available = true;
   ForEachElf(file, nullptr,
-             [=](const ElfFile& elf, string_view /*filename*/,
-                 uint32_t /*index_base*/) {
+             [&capstone_available, arch, mode](const ElfFile& elf,
+                                               string_view /*filename*/,
+                                               uint32_t /*index_base*/) {
                // Last .o file wins?  (For .a files)?  It's kind of arbitrary,
                // but a single .a file shouldn't have multiple archs in it.
-               ElfMachineToCapstone(elf.header().e_machine, arch, mode);
+               capstone_available &=
+                   ElfMachineToCapstone(elf.header().e_machine, arch, mode);
              });
+  return capstone_available;
 }
 
 static void ReadELFSymbols(const InputFile& file, RangeSink* sink,
@@ -838,7 +848,7 @@
   bool is_object = IsObjectFile(file.data());
   DisassemblyInfo info;
   DisassemblyInfo* infop = &info;
-  ReadElfArchMode(file, &info.arch, &info.mode);
+  bool capstone_available = ReadElfArchMode(file, &info.arch, &info.mode);
 
   ForEachElf(
       file, sink,
@@ -882,7 +892,7 @@
             string_view name = strtab_section.ReadString(sym.st_name);
             uint64_t full_addr =
                 ToVMAddr(sym.st_value, index_base + sym.st_shndx, is_object);
-            if (sink && !disassemble) {
+            if (sink && !(capstone_available && disassemble)) {
               sink->AddVMRangeAllowAlias(
                   "elf_symbols", full_addr, sym.st_size,
                   ItaniumDemangle(name, sink->data_source()));
@@ -891,7 +901,8 @@
               table->insert(
                   std::make_pair(name, std::make_pair(full_addr, sym.st_size)));
             }
-            if (disassemble && ELF64_ST_TYPE(sym.st_info) == STT_FUNC) {
+            if (capstone_available && disassemble &&
+                ELF64_ST_TYPE(sym.st_info) == STT_FUNC) {
               if (verbose_level > 1) {
                 printf("Disassembling function: %s\n", name.data());
               }
@@ -1401,8 +1412,7 @@
       info->start_address = vmaddr;
     }
 
-    ReadElfArchMode(file_data(), &info->arch, &info->mode);
-    return true;
+    return ReadElfArchMode(file_data(), &info->arch, &info->mode);
   }
 };
 
diff --git a/src/pe.cc b/src/pe.cc
index d5e01d3..4a0364b 100644
--- a/src/pe.cc
+++ b/src/pe.cc
@@ -23,39 +23,43 @@
 const uint16_t dos_magic = 0x5A4D;  // MZ
 
 //! Sizes in bytes of various things in the COFF format.
-namespace STRUCT_SIZES {
-enum {
-  Header16Size = 20,
-  Header32Size = 56,
-  NameSize = 8,
-  Symbol16Size = 18,
-  Symbol32Size = 20,
-  SectionSize = 40,
-  RelocationSize = 10,
-  BaseRelocationBlockSize = 8,
-  ImportDirectoryTableEntrySize = 20,
-  ResourceDirectoryTableSize = 16,
-  ResourceDirectoryEntriesSize = 8,
-  ResourceDataEntrySize = 16
+enum class StructSizes {
+  kHeader16Size = 20,
+  kHeader32Size = 56,
+  kNameSize = 8,
+  kSymbol16Size = 18,
+  kSymbol32Size = 20,
+  kSectionSize = 40,
+  kRelocationSize = 10,
+  kBaseRelocationBlockSize = 8,
+  kImportDirectoryTableEntrySize = 20,
+  kResourceDirectoryTableSize = 16,
+  kResourceDirectoryEntriesSize = 8,
+  kResourceDataEntrySize = 16,
 };
-}
 
 #include "third_party/lief_pe/pe_structures.h"
 
-static_assert(STRUCT_SIZES::SectionSize == sizeof(pe_section), "Compiler options broke LIEF struct layout");
-static_assert(STRUCT_SIZES::RelocationSize == sizeof(pe_relocation), "Compiler options broke LIEF struct layout");
-static_assert(STRUCT_SIZES::BaseRelocationBlockSize ==
+static_assert(static_cast<size_t>(StructSizes::kSectionSize) ==
+                  sizeof(pe_section),
+              "Compiler options broke LIEF struct layout");
+static_assert(static_cast<size_t>(StructSizes::kRelocationSize) ==
+                  sizeof(pe_relocation),
+              "Compiler options broke LIEF struct layout");
+static_assert(static_cast<size_t>(StructSizes::kBaseRelocationBlockSize) ==
                   sizeof(pe_base_relocation_block),
               "Compiler options broke LIEF struct layout");
-static_assert(STRUCT_SIZES::ImportDirectoryTableEntrySize == sizeof(pe_import),
-              "Compiler options broke LIEF struct layout");
-static_assert(STRUCT_SIZES::ResourceDirectoryTableSize ==
+static_assert(
+    static_cast<size_t>(StructSizes::kImportDirectoryTableEntrySize) ==
+        sizeof(pe_import),
+    "Compiler options broke LIEF struct layout");
+static_assert(static_cast<size_t>(StructSizes::kResourceDirectoryTableSize) ==
                   sizeof(pe_resource_directory_table),
               "Compiler options broke LIEF struct layout");
-static_assert(STRUCT_SIZES::ResourceDirectoryEntriesSize ==
+static_assert(static_cast<size_t>(StructSizes::kResourceDirectoryEntriesSize) ==
                   sizeof(pe_resource_directory_entries),
               "Compiler options broke LIEF struct layout");
-static_assert(STRUCT_SIZES::ResourceDataEntrySize ==
+static_assert(static_cast<size_t>(StructSizes::kResourceDataEntrySize) ==
                   sizeof(pe_resource_data_entry),
               "Compiler options broke LIEF struct layout");
 
@@ -159,8 +163,9 @@
     // For longer names, this member contains a forward slash (/) followed by an
     // ASCII representation of a decimal number that is an offset into the
     // string table.
-    name = std::string(header_.Name,
-                       strnlen(header_.Name, STRUCT_SIZES::NameSize));
+    name = std::string(
+        header_.Name,
+        strnlen(header_.Name, static_cast<size_t>(StructSizes::kNameSize)));
   }
 
  private:
diff --git a/src/range_map.h b/src/range_map.h
index 9c13bbc..aab0771 100644
--- a/src/range_map.h
+++ b/src/range_map.h
@@ -268,6 +268,7 @@
     return;
   }
 
+  iters.reserve(range_maps.size());
   for (auto range_map : range_maps) {
     iters.push_back(range_map->mappings_.begin());
   }
diff --git a/src/re.h b/src/re.h
index d990767..e415c42 100644
--- a/src/re.h
+++ b/src/re.h
@@ -22,7 +22,6 @@
 #endif
 
 #include "absl/base/attributes.h"
-#include "bloaty.h"
 
 namespace bloaty {
 
diff --git a/src/util.h b/src/util.h
index 1aad13f..b934027 100644
--- a/src/util.h
+++ b/src/util.h
@@ -134,7 +134,7 @@
 }
 
 template <class T, size_t N = sizeof(T)> T ReadFixed(absl::string_view *data) {
-  static_assert(N <= sizeof(N), "N too big for this data type");
+  static_assert(N <= sizeof(T), "N too big for this data type");
   T val = 0;
   if (data->size() < N) {
     THROW("premature EOF reading fixed-length data");
diff --git a/tests/bloaty_test.cc b/tests/bloaty_test.cc
index 2783ac8..8187aca 100644
--- a/tests/bloaty_test.cc
+++ b/tests/bloaty_test.cc
@@ -253,8 +253,9 @@
   ASSERT_TRUE(GetFileSize(file1, &size1));
   ASSERT_TRUE(GetFileSize(file2, &size2));
   RunBloaty({"bloaty", file1, file2, "-d", "inputfiles"});
-  AssertChildren(*top_row_, {std::make_tuple(file1, kUnknown, size1),
-                             std::make_tuple(file2, kUnknown, size2)});
+  AssertChildren(*top_row_,
+                 {std::make_tuple(file1, kUnknown, static_cast<int>(size1)),
+                  std::make_tuple(file2, kUnknown, static_cast<int>(size2))});
 
   // Should work with custom data sources.
   bloaty::Options options;
@@ -273,8 +274,8 @@
   )", &options);
 
   RunBloatyWithOptions(options, bloaty::OutputOptions());
-  AssertChildren(*top_row_,
-                 {std::make_tuple("binary", kUnknown, size1 + size2)});
+  AssertChildren(*top_row_, {std::make_tuple("binary", kUnknown,
+                                             static_cast<int>(size1 + size2))});
 }
 
 TEST_F(BloatyTest, DiffMode) {
diff --git a/tests/fuzz_driver.cc b/tests/fuzz_driver.cc
index 2e286d4..4980317 100644
--- a/tests/fuzz_driver.cc
+++ b/tests/fuzz_driver.cc
@@ -21,7 +21,7 @@
 
 int main(int argc, char **argv) {
   for (int i = 1; i < argc; i++) {
-    std::ifstream in(argv[i]);
+    std::ifstream in(argv[i], std::ios_base::in | std::ios_base::binary);
     in.seekg(0, in.end);
     size_t length = in.tellg();
     in.seekg (0, in.beg);