Source: Restructure Source::File

Add Source::FileContent to hold the file source content and per-line data.

Have Source hold an optional pointer to a FileContent, and add a file_path field.

This allows us to kill the `FreeInternalCompilerErrors()` filth as we're now able to construct Sources that hold a file path without file content.

Change-Id: I03556795d7d4161c3d34cef32cb685c45ad04a3d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/42026
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/clone_context_test.cc b/src/clone_context_test.cc
index fc7b73a..1a97fb8 100644
--- a/src/clone_context_test.cc
+++ b/src/clone_context_test.cc
@@ -280,13 +280,6 @@
         ctx.Clone(original_root);
       },
       "internal compiler error");
-
-  // Ensure that this test does not leak memory.
-  // This will be automatically called by main() in src/test_main.cc, but
-  // chromium uses it's own test entry point.
-  // TODO(ben-clayton): Add this call to the end of Chromium's main(), and we
-  // can remove this call.
-  FreeInternalCompilerErrors();
 }
 
 }  // namespace
diff --git a/src/debug.cc b/src/debug.cc
index 01305ec..2ac9ce7 100644
--- a/src/debug.cc
+++ b/src/debug.cc
@@ -27,45 +27,8 @@
 
 InternalCompilerErrorReporter* ice_reporter = nullptr;
 
-/// Note - this class is _not_ thread safe. If we have multiple internal
-/// compiler errors occurring at the same time on different threads, then
-/// we're in serious trouble.
-class SourceFileToDelete {
-  static SourceFileToDelete* instance;
-
- public:
-  /// Adds file to the list that will be deleted on call to Free()
-  static void Add(Source::File* file) {
-    if (!instance) {
-      instance = new SourceFileToDelete();
-    }
-    instance->files.emplace_back(file);
-  }
-
-  /// Free deletes all the source files added by calls to Add() and then this
-  /// SourceFileToDelete object.
-  static void Free() {
-    if (instance) {
-      for (auto* file : instance->files) {
-        delete file;
-      }
-      delete instance;
-      instance = nullptr;
-    }
-  }
-
- private:
-  std::vector<Source::File*> files;
-};
-
-SourceFileToDelete* SourceFileToDelete::instance = nullptr;
-
 }  // namespace
 
-void FreeInternalCompilerErrors() {
-  SourceFileToDelete::Free();
-}
-
 void SetInternalCompilerErrorReporter(InternalCompilerErrorReporter* reporter) {
   ice_reporter = reporter;
 }
@@ -76,11 +39,7 @@
     : file_(file), line_(line), diagnostics_(diagnostics) {}
 
 InternalCompilerError::~InternalCompilerError() {
-  auto* file = new Source::File(file_, "");
-
-  SourceFileToDelete::Add(file);
-
-  Source source{Source::Range{Source::Location{line_}}, file};
+  Source source{Source::Range{Source::Location{line_}}, file_};
   diagnostics_.add_ice(msg_.str(), source);
 
   if (ice_reporter) {
diff --git a/src/debug.h b/src/debug.h
index 56649fd..80c597f 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -27,12 +27,6 @@
 /// Function type used for registering an internal compiler error reporter
 using InternalCompilerErrorReporter = void(const diag::List&);
 
-/// Frees any memory allocated for reporting internal compiler errors.
-/// Must only be called on application termination.
-/// If an internal compiler error is raised and this function is not called,
-/// then memory will leak.
-void FreeInternalCompilerErrors();
-
 /// Sets the global error reporter to be called in case of internal compiler
 /// errors.
 /// @param reporter the error reporter
diff --git a/src/debug_test.cc b/src/debug_test.cc
index 5b9df293..45e8a1f 100644
--- a/src/debug_test.cc
+++ b/src/debug_test.cc
@@ -26,13 +26,6 @@
         TINT_UNREACHABLE(diagnostics);
       },
       "internal compiler error");
-
-  // Ensure that this test does not leak memory.
-  // This will be automatically called by main() in src/test_main.cc, but
-  // chromium uses it's own test entry point.
-  // TODO(ben-clayton): Add this call to the end of Chromium's main(), and we
-  // can remove this call.
-  FreeInternalCompilerErrors();
 }
 
 }  // namespace
diff --git a/src/diagnostic/formatter.cc b/src/diagnostic/formatter.cc
index 4e96d2b..4acecab 100644
--- a/src/diagnostic/formatter.cc
+++ b/src/diagnostic/formatter.cc
@@ -160,12 +160,12 @@
   std::vector<TextAndColor> prefix;
   prefix.reserve(6);
 
-  if (style_.print_file && src.file != nullptr && !src.file->path.empty()) {
+  if (style_.print_file && !src.file_path.empty()) {
     if (rng.begin.line > 0) {
-      prefix.emplace_back(TextAndColor{src.file->path + ":" + to_str(rng.begin),
+      prefix.emplace_back(TextAndColor{src.file_path + ":" + to_str(rng.begin),
                                        Color::kDefault});
     } else {
-      prefix.emplace_back(TextAndColor{src.file->path, Color::kDefault});
+      prefix.emplace_back(TextAndColor{src.file_path, Color::kDefault});
     }
   } else if (rng.begin.line > 0) {
     prefix.emplace_back(TextAndColor{to_str(rng.begin), Color::kDefault});
@@ -208,15 +208,15 @@
   }
   state << diag.message;
 
-  if (style_.print_line && src.file != nullptr && rng.begin.line > 0) {
+  if (style_.print_line && src.file_content != nullptr && rng.begin.line > 0) {
     state.newline();
     state.set_style({Color::kDefault, false});
 
     for (size_t line = rng.begin.line; line <= rng.end.line; line++) {
-      if (line < src.file->lines.size() + 1) {
-        auto len = src.file->lines[line - 1].size();
+      if (line < src.file_content->lines.size() + 1) {
+        auto len = src.file_content->lines[line - 1].size();
 
-        state << src.file->lines[line - 1];
+        state << src.file_content->lines[line - 1];
 
         state.newline();
         state.set_style({Color::kCyan, false});
diff --git a/src/reader/wgsl/lexer.cc b/src/reader/wgsl/lexer.cc
index 181f46c..2939e66 100644
--- a/src/reader/wgsl/lexer.cc
+++ b/src/reader/wgsl/lexer.cc
@@ -32,9 +32,10 @@
 
 }  // namespace
 
-Lexer::Lexer(Source::File const* file)
-    : file_(file),
-      len_(static_cast<uint32_t>(file->content.size())),
+Lexer::Lexer(const std::string& file_path, const Source::FileContent* content)
+    : file_path_(file_path),
+      content_(content),
+      len_(static_cast<uint32_t>(content->data.size())),
       location_{1, 1} {}
 
 Lexer::~Lexer() = default;
@@ -82,7 +83,8 @@
 
 Source Lexer::begin_source() const {
   Source src{};
-  src.file = file_;
+  src.file_path = file_path_;
+  src.file_content = content_;
   src.range.begin = location_;
   src.range.end = location_;
   return src;
@@ -115,13 +117,13 @@
 bool Lexer::matches(size_t pos, const std::string& substr) {
   if (pos >= len_)
     return false;
-  return file_->content.substr(pos, substr.size()) == substr;
+  return content_->data.substr(pos, substr.size()) == substr;
 }
 
 void Lexer::skip_whitespace() {
   for (;;) {
     auto pos = pos_;
-    while (!is_eof() && is_whitespace(file_->content[pos_])) {
+    while (!is_eof() && is_whitespace(content_->data[pos_])) {
       if (matches(pos_, "\n")) {
         pos_++;
         location_.line++;
@@ -162,7 +164,7 @@
   if (matches(end, "-")) {
     end++;
   }
-  while (end < len_ && is_digit(file_->content[end])) {
+  while (end < len_ && is_digit(content_->data[end])) {
     end++;
   }
 
@@ -171,7 +173,7 @@
   }
   end++;
 
-  while (end < len_ && is_digit(file_->content[end])) {
+  while (end < len_ && is_digit(content_->data[end])) {
     end++;
   }
 
@@ -183,7 +185,7 @@
     }
 
     auto exp_start = end;
-    while (end < len_ && isdigit(file_->content[end])) {
+    while (end < len_ && isdigit(content_->data[end])) {
       end++;
     }
 
@@ -192,7 +194,7 @@
       return {};
   }
 
-  auto str = file_->content.substr(start, end - start);
+  auto str = content_->data.substr(start, end - start);
   if (str == "." || str == "-.")
     return {};
 
@@ -201,7 +203,7 @@
 
   end_source(source);
 
-  auto res = strtod(file_->content.c_str() + start, nullptr);
+  auto res = strtod(content_->data.c_str() + start, nullptr);
   // This handles if the number is a really small in the exponent
   if (res > 0 && res < static_cast<double>(std::numeric_limits<float>::min())) {
     return {Token::Type::kError, source, "f32 (" + str + " too small"};
@@ -221,13 +223,13 @@
                                               size_t start,
                                               size_t end,
                                               int32_t base) {
-  auto res = strtoll(file_->content.c_str() + start, nullptr, base);
+  auto res = strtoll(content_->data.c_str() + start, nullptr, base);
   if (matches(pos_, "u")) {
     if (static_cast<uint64_t>(res) >
         static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
       return {
           Token::Type::kError, source,
-          "u32 (" + file_->content.substr(start, end - start) + ") too large"};
+          "u32 (" + content_->data.substr(start, end - start) + ") too large"};
     }
     pos_ += 1;
     location_.column += 1;
@@ -238,12 +240,12 @@
   if (res < static_cast<int64_t>(std::numeric_limits<int32_t>::min())) {
     return {
         Token::Type::kError, source,
-        "i32 (" + file_->content.substr(start, end - start) + ") too small"};
+        "i32 (" + content_->data.substr(start, end - start) + ") too small"};
   }
   if (res > static_cast<int64_t>(std::numeric_limits<int32_t>::max())) {
     return {
         Token::Type::kError, source,
-        "i32 (" + file_->content.substr(start, end - start) + ") too large"};
+        "i32 (" + content_->data.substr(start, end - start) + ") too large"};
   }
   end_source(source);
   return {source, static_cast<int32_t>(res)};
@@ -263,7 +265,7 @@
   }
   end += 2;
 
-  while (!is_eof() && is_hex(file_->content[end])) {
+  while (!is_eof() && is_hex(content_->data[end])) {
     end += 1;
   }
 
@@ -282,18 +284,18 @@
   if (matches(end, "-")) {
     end++;
   }
-  if (end >= len_ || !is_digit(file_->content[end])) {
+  if (end >= len_ || !is_digit(content_->data[end])) {
     return {};
   }
 
   auto first = end;
-  while (end < len_ && is_digit(file_->content[end])) {
+  while (end < len_ && is_digit(content_->data[end])) {
     end++;
   }
 
   // If the first digit is a zero this must only be zero as leading zeros
   // are not allowed.
-  if (file_->content[first] == '0' && (end - first != 1))
+  if (content_->data[first] == '0' && (end - first != 1))
     return {};
 
   pos_ = end;
@@ -304,19 +306,19 @@
 
 Token Lexer::try_ident() {
   // Must begin with an a-zA-Z_
-  if (!is_alpha(file_->content[pos_])) {
+  if (!is_alpha(content_->data[pos_])) {
     return {};
   }
 
   auto source = begin_source();
 
   auto s = pos_;
-  while (!is_eof() && is_alphanum(file_->content[pos_])) {
+  while (!is_eof() && is_alphanum(content_->data[pos_])) {
     pos_++;
     location_.column++;
   }
 
-  auto str = file_->content.substr(s, pos_ - s);
+  auto str = content_->data.substr(s, pos_ - s);
   auto t = check_reserved(source, str);
   if (!t.IsUninitialized()) {
     return t;
@@ -352,7 +354,7 @@
   end_source(source);
 
   return {Token::Type::kStringLiteral, source,
-          file_->content.substr(start, end - start)};
+          content_->data.substr(start, end - start)};
 }
 
 Token Lexer::try_punctuation() {
diff --git a/src/reader/wgsl/lexer.h b/src/reader/wgsl/lexer.h
index ef1a839..1fd550a 100644
--- a/src/reader/wgsl/lexer.h
+++ b/src/reader/wgsl/lexer.h
@@ -28,8 +28,9 @@
 class Lexer {
  public:
   /// Creates a new Lexer
-  /// @param file the input file to parse
-  explicit Lexer(Source::File const* file);
+  /// @param file_path the path to the file containing the source
+  /// @param content the source content
+  Lexer(const std::string& file_path, const Source::FileContent* content);
   ~Lexer();
 
   /// Returns the next token in the input stream
@@ -63,8 +64,10 @@
   bool is_alphanum(char ch) const;
   bool matches(size_t pos, const std::string& substr);
 
-  /// The source to parse
-  Source::File const* file_;
+  /// The source file path
+  std::string const file_path_;
+  /// The source file content
+  Source::FileContent const* const content_;
   /// The length of the input
   uint32_t len_ = 0;
   /// The current position within the input
diff --git a/src/reader/wgsl/lexer_test.cc b/src/reader/wgsl/lexer_test.cc
index 7a4a49d..fe6d9eb 100644
--- a/src/reader/wgsl/lexer_test.cc
+++ b/src/reader/wgsl/lexer_test.cc
@@ -26,15 +26,15 @@
 using LexerTest = testing::Test;
 
 TEST_F(LexerTest, Empty) {
-  Source::File file("test.wgsl", "");
-  Lexer l(&file);
+  Source::FileContent content("");
+  Lexer l("test.wgsl", &content);
   auto t = l.next();
   EXPECT_TRUE(t.IsEof());
 }
 
 TEST_F(LexerTest, Skips_Whitespace) {
-  Source::File file("test.wgsl", "\t\r\n\t    ident\t\n\t  \r ");
-  Lexer l(&file);
+  Source::FileContent content("\t\r\n\t    ident\t\n\t  \r ");
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsIdentifier());
@@ -49,11 +49,11 @@
 }
 
 TEST_F(LexerTest, Skips_Comments) {
-  Source::File file("test.wgsl", R"(//starts with comment
+  Source::FileContent content(R"(//starts with comment
 ident1 //ends with comment
 // blank line
  ident2)");
-  Lexer l(&file);
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsIdentifier());
@@ -76,8 +76,8 @@
 }
 
 TEST_F(LexerTest, StringTest_Parse) {
-  Source::File file("test.wgsl", R"(id "this is string content" id2)");
-  Lexer l(&file);
+  Source::FileContent content(R"(id "this is string content" id2)");
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsIdentifier());
@@ -105,8 +105,8 @@
 }
 
 TEST_F(LexerTest, StringTest_Unterminated) {
-  Source::File file("test.wgsl", R"(id "this is string content)");
-  Lexer l(&file);
+  Source::FileContent content(R"(id "this is string content)");
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsIdentifier());
@@ -139,8 +139,8 @@
 using FloatTest = testing::TestWithParam<FloatData>;
 TEST_P(FloatTest, Parse) {
   auto params = GetParam();
-  Source::File file("test.wgsl", params.input);
-  Lexer l(&file);
+  Source::FileContent content(params.input);
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsFloatLiteral());
@@ -175,8 +175,8 @@
 
 using FloatTest_Invalid = testing::TestWithParam<const char*>;
 TEST_P(FloatTest_Invalid, Handles) {
-  Source::File file("test.wgsl", GetParam());
-  Lexer l(&file);
+  Source::FileContent content(GetParam());
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_FALSE(t.IsFloatLiteral());
@@ -193,8 +193,8 @@
 
 using IdentifierTest = testing::TestWithParam<const char*>;
 TEST_P(IdentifierTest, Parse) {
-  Source::File file("test.wgsl", GetParam());
-  Lexer l(&file);
+  Source::FileContent content(GetParam());
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsIdentifier());
@@ -210,8 +210,8 @@
     testing::Values("test01", "_test_", "test_", "_test", "_01", "_test01"));
 
 TEST_F(LexerTest, IdentifierTest_DoesNotStartWithNumber) {
-  Source::File file("test.wgsl", "01test");
-  Lexer l(&file);
+  Source::FileContent content("01test");
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_FALSE(t.IsIdentifier());
@@ -229,8 +229,8 @@
 using IntegerTest_HexSigned = testing::TestWithParam<HexSignedIntData>;
 TEST_P(IntegerTest_HexSigned, Matches) {
   auto params = GetParam();
-  Source::File file("test.wgsl", params.input);
-  Lexer l(&file);
+  Source::FileContent content(params.input);
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsSintLiteral());
@@ -252,16 +252,18 @@
         HexSignedIntData{"0x7FFFFFFF", std::numeric_limits<int32_t>::max()}));
 
 TEST_F(LexerTest, IntegerTest_HexSignedTooLarge) {
-  Source::File file("test.wgsl", "0x80000000");
-  Lexer l(&file);
+  Source::FileContent content("0x80000000");
+  Lexer l("test.wgsl", &content);
+
   auto t = l.next();
   ASSERT_TRUE(t.IsError());
   EXPECT_EQ(t.to_str(), "i32 (0x80000000) too large");
 }
 
 TEST_F(LexerTest, IntegerTest_HexSignedTooSmall) {
-  Source::File file("test.wgsl", "-0x8000000F");
-  Lexer l(&file);
+  Source::FileContent content("-0x8000000F");
+  Lexer l("test.wgsl", &content);
+
   auto t = l.next();
   ASSERT_TRUE(t.IsError());
   EXPECT_EQ(t.to_str(), "i32 (-0x8000000F) too small");
@@ -278,8 +280,8 @@
 using IntegerTest_HexUnsigned = testing::TestWithParam<HexUnsignedIntData>;
 TEST_P(IntegerTest_HexUnsigned, Matches) {
   auto params = GetParam();
-  Source::File file("test.wgsl", params.input);
-  Lexer l(&file);
+  Source::FileContent content(params.input);
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsUintLiteral());
@@ -304,8 +306,9 @@
                                        std::numeric_limits<uint32_t>::max()}));
 
 TEST_F(LexerTest, IntegerTest_HexUnsignedTooLarge) {
-  Source::File file("test.wgsl", "0xffffffffffu");
-  Lexer l(&file);
+  Source::FileContent content("0xffffffffffu");
+  Lexer l("test.wgsl", &content);
+
   auto t = l.next();
   ASSERT_TRUE(t.IsError());
   EXPECT_EQ(t.to_str(), "u32 (0xffffffffff) too large");
@@ -322,8 +325,8 @@
 using IntegerTest_Unsigned = testing::TestWithParam<UnsignedIntData>;
 TEST_P(IntegerTest_Unsigned, Matches) {
   auto params = GetParam();
-  Source::File file("test.wgsl", params.input);
-  Lexer l(&file);
+  Source::FileContent content(params.input);
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsUintLiteral());
@@ -351,8 +354,8 @@
 using IntegerTest_Signed = testing::TestWithParam<SignedIntData>;
 TEST_P(IntegerTest_Signed, Matches) {
   auto params = GetParam();
-  Source::File file("test.wgsl", params.input);
-  Lexer l(&file);
+  Source::FileContent content(params.input);
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsSintLiteral());
@@ -374,8 +377,8 @@
 
 using IntegerTest_Invalid = testing::TestWithParam<const char*>;
 TEST_P(IntegerTest_Invalid, Parses) {
-  Source::File file("test.wgsl", GetParam());
-  Lexer l(&file);
+  Source::FileContent content(GetParam());
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_FALSE(t.IsSintLiteral());
@@ -396,8 +399,8 @@
 using PunctuationTest = testing::TestWithParam<TokenData>;
 TEST_P(PunctuationTest, Parses) {
   auto params = GetParam();
-  Source::File file("test.wgsl", params.input);
-  Lexer l(&file);
+  Source::FileContent content(params.input);
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.Is(params.type));
@@ -450,8 +453,8 @@
 using KeywordTest = testing::TestWithParam<TokenData>;
 TEST_P(KeywordTest, Parses) {
   auto params = GetParam();
-  Source::File file("test.wgsl", params.input);
-  Lexer l(&file);
+  Source::FileContent content(params.input);
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.Is(params.type)) << params.input;
@@ -582,8 +585,8 @@
 using KeywordTest_Reserved = testing::TestWithParam<const char*>;
 TEST_P(KeywordTest_Reserved, Parses) {
   auto* keyword = GetParam();
-  Source::File file("test.wgsl", keyword);
-  Lexer l(&file);
+  Source::FileContent content(keyword);
+  Lexer l("test.wgsl", &content);
 
   auto t = l.next();
   EXPECT_TRUE(t.IsReservedKeyword());
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 0000298..727603a 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -216,7 +216,7 @@
     const FunctionHeader& rhs) = default;
 
 ParserImpl::ParserImpl(Source::File const* file)
-    : lexer_(std::make_unique<Lexer>(file)) {}
+    : lexer_(std::make_unique<Lexer>(file->path, &file->content)) {}
 
 ParserImpl::~ParserImpl() = default;
 
@@ -294,7 +294,7 @@
     }
     expect_global_decl();
     if (diags_.error_count() >= max_errors_) {
-      add_error(Source{{}, p.source().file},
+      add_error(Source{{}, p.source().file_path},
                 "stopping after " + std::to_string(max_errors_) + " errors");
       break;
     }
diff --git a/src/reader/wgsl/token_test.cc b/src/reader/wgsl/token_test.cc
index ae56acc..24552f3 100644
--- a/src/reader/wgsl/token_test.cc
+++ b/src/reader/wgsl/token_test.cc
@@ -67,9 +67,7 @@
 }
 
 TEST_F(TokenTest, Source) {
-  Source::File file("", "");
   Source src;
-  src.file = &file;
   src.range.begin = Source::Location{3, 9};
   src.range.end = Source::Location{4, 3};
 
@@ -78,7 +76,6 @@
   EXPECT_EQ(t.source().range.begin.column, 9u);
   EXPECT_EQ(t.source().range.end.line, 4u);
   EXPECT_EQ(t.source().range.end.column, 3u);
-  EXPECT_EQ(t.source().file, &file);
 }
 
 }  // namespace
diff --git a/src/source.cc b/src/source.cc
index bd45fd5..f4a7236 100644
--- a/src/source.cc
+++ b/src/source.cc
@@ -30,9 +30,10 @@
 }
 }  // namespace
 
-Source::File::File(const std::string& file_path,
-                   const std::string& file_content)
-    : path(file_path), content(file_content), lines(split_lines(content)) {}
+Source::FileContent::FileContent(const std::string& body)
+    : data(body), lines(split_lines(body)) {}
+
+Source::FileContent::~FileContent() = default;
 
 Source::File::~File() = default;
 
diff --git a/src/source.h b/src/source.h
index a6e7830..b77fa02 100644
--- a/src/source.h
+++ b/src/source.h
@@ -18,6 +18,7 @@
 
 #include <stddef.h>
 
+#include <ostream>
 #include <string>
 #include <vector>
 
@@ -26,21 +27,37 @@
 /// Source describes a range of characters within a source file.
 class Source {
  public:
+  /// FileContent describes the content of a source file.
+  class FileContent {
+   public:
+    /// Constructs the FileContent with the given file content.
+    /// @param data the file contents
+    explicit FileContent(const std::string& data);
+
+    /// Destructor
+    ~FileContent();
+
+    /// un-split file content
+    const std::string data;
+    /// #data split by lines
+    const std::vector<std::string> lines;
+  };
+
   /// File describes a source file, including path and content.
   class File {
    public:
     /// Constructs the File with the given file path and content.
-    /// @param file_path the path for this file
-    /// @param file_content the file contents
-    File(const std::string& file_path, const std::string& file_content);
+    /// @param p the path for this file
+    /// @param c the file contents
+    inline File(const std::string& p, const std::string& c)
+        : path(p), content(c) {}
+
     ~File();
 
     /// file path (optional)
     const std::string path;
     /// file content
-    const std::string content;
-    /// #content split by lines
-    const std::vector<std::string> lines;
+    const FileContent content;
   };
 
   /// Location holds a 1-based line and column index.
@@ -74,7 +91,7 @@
   };
 
   /// Constructs the Source with an zero initialized Range and null File.
-  inline Source() = default;
+  inline Source() : range() {}
 
   /// Constructs the Source with the Range `rng` and a null File
   /// @param rng the source range
@@ -84,17 +101,40 @@
   /// @param loc the start and end location for the source range
   inline explicit Source(const Location& loc) : range(Range(loc)) {}
 
-  /// Constructs the Source with the Range `rng` and File `f`
+  /// Constructs the Source with the Range `rng` and File `file`
   /// @param rng the source range
-  /// @param f the source file
-  inline Source(const Range& rng, File const* f) : range(rng), file(f) {}
+  /// @param file the source file
+  inline Source(const Range& rng, File const* file)
+      : range(rng), file_path(file->path), file_content(&file->content) {}
 
-  /// range is the span of text this source refers to in #file
+  /// Constructs the Source with the Range `rng`, file path `path` and content
+  /// `content`
+  /// @param rng the source range
+  /// @param path the source file path
+  /// @param content the source file content
+  inline Source(const Range& rng,
+                const std::string& path,
+                FileContent* content = nullptr)
+      : range(rng), file_path(path), file_content(content) {}
+
+  /// range is the span of text this source refers to in #file_path
   Range range;
-  /// file is the source file this source refers to
-  File const* file = nullptr;
+  /// file is the optional file path this source refers to
+  std::string file_path;
+  /// file is the optional source content this source refers to
+  const FileContent* file_content = nullptr;
 };
 
+/// Writes the Source::FileContent to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param content the file content to write
+/// @returns out so calls can be chained
+inline std::ostream& operator<<(std::ostream& out,
+                                const Source::FileContent& content) {
+  out << content.data;
+  return out;
+}
+
 }  // namespace tint
 
 #endif  // SRC_SOURCE_H_
diff --git a/src/test_main.cc b/src/test_main.cc
index 5fca747..7a8b394 100644
--- a/src/test_main.cc
+++ b/src/test_main.cc
@@ -32,7 +32,5 @@
 
   auto res = RUN_ALL_TESTS();
 
-  tint::FreeInternalCompilerErrors();
-
   return res;
 }