Loading tools/aapt2/LoadedApk.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -225,7 +225,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table } } else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) { pb::ResourceTable pb_table; SerializeTableToPb(*split_table, &pb_table); SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics()); if (!io::CopyProtoToArchive(context, &pb_table, path, Loading tools/aapt2/StringPool.cpp +78 −29 Original line number Diff line number Diff line Loading @@ -332,6 +332,25 @@ static T* EncodeLength(T* data, size_t length) { return data; } /** * Returns the maximum possible string length that can be successfully encoded * using 2 units of the specified T. * EncodeLengthMax<char> -> maximum unit length of 0x7FFF * EncodeLengthMax<char16_t> -> maximum unit length of 0x7FFFFFFF **/ template <typename T> static size_t EncodeLengthMax() { static_assert(std::is_integral<T>::value, "wat."); constexpr size_t kMask = 1 << ((sizeof(T) * 8 * 2) - 1); constexpr size_t max = kMask - 1; return max; } /** * Returns the number of units (1 or 2) needed to encode the string length * before writing the string. */ template <typename T> static size_t EncodedLengthUnits(size_t length) { static_assert(std::is_integral<T>::value, "wat."); Loading @@ -341,15 +360,30 @@ static size_t EncodedLengthUnits(size_t length) { return length > kMaxSize ? 2 : 1; } static void EncodeString(const std::string& str, const bool utf8, BigBuffer* out) { const std::string kStringTooLarge = "STRING_TOO_LARGE"; static bool EncodeString(const std::string& str, const bool utf8, BigBuffer* out, IDiagnostics* diag) { if (utf8) { const std::string& encoded = str; const ssize_t utf16_length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size()); const ssize_t utf16_length = utf8_to_utf16_length( reinterpret_cast<const uint8_t*>(encoded.data()), encoded.size()); CHECK(utf16_length >= 0); const size_t total_size = EncodedLengthUnits<char>(utf16_length) + EncodedLengthUnits<char>(encoded.length()) + encoded.size() + 1; // Make sure the lengths to be encoded do not exceed the maximum length that // can be encoded using chars if ((((size_t)encoded.size()) > EncodeLengthMax<char>()) || (((size_t)utf16_length) > EncodeLengthMax<char>())) { diag->Error(DiagMessage() << "string too large to encode using UTF-8 " << "written instead as '" << kStringTooLarge << "'"); EncodeString(kStringTooLarge, utf8, out, diag); return false; } const size_t total_size = EncodedLengthUnits<char>(utf16_length) + EncodedLengthUnits<char>(encoded.size()) + encoded.size() + 1; char* data = out->NextBlock<char>(total_size); Loading @@ -357,15 +391,26 @@ static void EncodeString(const std::string& str, const bool utf8, BigBuffer* out data = EncodeLength(data, utf16_length); // Now encode the size of the real UTF8 string. data = EncodeLength(data, encoded.length()); data = EncodeLength(data, encoded.size()); strncpy(data, encoded.data(), encoded.size()); } else { const std::u16string encoded = util::Utf8ToUtf16(str); const ssize_t utf16_length = encoded.size(); // Make sure the length to be encoded does not exceed the maximum possible // length that can be encoded if (((size_t)utf16_length) > EncodeLengthMax<char16_t>()) { diag->Error(DiagMessage() << "string too large to encode using UTF-16 " << "written instead as '" << kStringTooLarge << "'"); EncodeString(kStringTooLarge, utf8, out, diag); return false; } // Total number of 16-bit words to write. const size_t total_size = EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1; const size_t total_size = EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1; char16_t* data = out->NextBlock<char16_t>(total_size); Loading @@ -380,9 +425,13 @@ static void EncodeString(const std::string& str, const bool utf8, BigBuffer* out // The null-terminating character is already here due to the block of data // being set to 0s on allocation. } return true; } bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag) { bool no_error = true; const size_t start_index = out->size(); android::ResStringPool_header* header = out->NextBlock<android::ResStringPool_header>(); header->header.type = util::HostToDevice16(android::RES_STRING_POOL_TYPE); Loading @@ -403,12 +452,12 @@ bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { // Styles always come first. for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) { *indices++ = out->size() - before_strings_index; EncodeString(entry->value, utf8, out); no_error = EncodeString(entry->value, utf8, out, diag) && no_error; } for (const std::unique_ptr<Entry>& entry : pool.strings_) { *indices++ = out->size() - before_strings_index; EncodeString(entry->value, utf8, out); no_error = EncodeString(entry->value, utf8, out, diag) && no_error; } out->Align4(); Loading Loading @@ -446,15 +495,15 @@ bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { out->Align4(); } header->header.size = util::HostToDevice32(out->size() - start_index); return true; return no_error; } bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool) { return Flatten(out, pool, true); bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) { return Flatten(out, pool, true, diag); } bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool) { return Flatten(out, pool, false); bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) { return Flatten(out, pool, false, diag); } } // namespace aapt tools/aapt2/StringPool.h +4 −3 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ #include "androidfw/StringPiece.h" #include "ConfigDescription.h" #include "Diagnostics.h" #include "util/BigBuffer.h" namespace aapt { Loading Loading @@ -152,8 +153,8 @@ class StringPool { int ref_; }; static bool FlattenUtf8(BigBuffer* out, const StringPool& pool); static bool FlattenUtf16(BigBuffer* out, const StringPool& pool); static bool FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag); static bool FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag); StringPool() = default; StringPool(StringPool&&) = default; Loading Loading @@ -207,7 +208,7 @@ class StringPool { private: DISALLOW_COPY_AND_ASSIGN(StringPool); static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8); static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag); Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique); void ReAssignIndices(); Loading tools/aapt2/StringPool_test.cpp +57 −4 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include "androidfw/StringPiece.h" #include "Diagnostics.h" #include "test/Test.h" #include "util/Util.h" Loading Loading @@ -188,10 +189,11 @@ TEST(StringPoolTest, StylesAndStringsAreSeparateAfterSorting) { TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { using namespace android; // For NO_ERROR on Windows. StdErrDiagnostics diag; StringPool pool; BigBuffer buffer(1024); StringPool::FlattenUtf8(&buffer, pool); StringPool::FlattenUtf8(&buffer, pool, &diag); std::unique_ptr<uint8_t[]> data = util::Copy(buffer); ResStringPool test; Loading @@ -200,11 +202,12 @@ TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { TEST(StringPoolTest, FlattenOddCharactersUtf16) { using namespace android; // For NO_ERROR on Windows. StdErrDiagnostics diag; StringPool pool; pool.MakeRef("\u093f"); BigBuffer buffer(1024); StringPool::FlattenUtf16(&buffer, pool); StringPool::FlattenUtf16(&buffer, pool, &diag); std::unique_ptr<uint8_t[]> data = util::Copy(buffer); ResStringPool test; Loading @@ -225,6 +228,7 @@ constexpr const char* sLongString = TEST(StringPoolTest, Flatten) { using namespace android; // For NO_ERROR on Windows. StdErrDiagnostics diag; StringPool pool; Loading @@ -244,8 +248,8 @@ TEST(StringPoolTest, Flatten) { EXPECT_THAT(ref_d.index(), Eq(4u)); BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; StringPool::FlattenUtf8(&buffers[0], pool); StringPool::FlattenUtf16(&buffers[1], pool); StringPool::FlattenUtf8(&buffers[0], pool, &diag); StringPool::FlattenUtf16(&buffers[1], pool, &diag); // Test both UTF-8 and UTF-16 buffers. for (const BigBuffer& buffer : buffers) { Loading Loading @@ -288,4 +292,53 @@ TEST(StringPoolTest, Flatten) { } } TEST(StringPoolTest, MaxEncodingLength) { StdErrDiagnostics diag; using namespace android; // For NO_ERROR on Windows. ResStringPool test; StringPool pool; pool.MakeRef("aaaaaaaaaa"); BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; // Make sure a UTF-8 string under the maximum length does not produce an error EXPECT_THAT(StringPool::FlattenUtf8(&buffers[0], pool, &diag), Eq(true)); std::unique_ptr<uint8_t[]> data = util::Copy(buffers[0]); test.setTo(data.get(), buffers[0].size()); EXPECT_THAT(util::GetString(test, 0), Eq("aaaaaaaaaa")); // Make sure a UTF-16 string under the maximum length does not produce an error EXPECT_THAT(StringPool::FlattenUtf16(&buffers[1], pool, &diag), Eq(true)); data = util::Copy(buffers[1]); test.setTo(data.get(), buffers[1].size()); EXPECT_THAT(util::GetString16(test, 0), Eq(u"aaaaaaaaaa")); StringPool pool2; std::string longStr(50000, 'a'); pool2.MakeRef("this fits1"); pool2.MakeRef(longStr); pool2.MakeRef("this fits2"); BigBuffer buffers2[2] = {BigBuffer(1024), BigBuffer(1024)}; // Make sure a string that exceeds the maximum length of UTF-8 produces an // error and writes a shorter error string instead EXPECT_THAT(StringPool::FlattenUtf8(&buffers2[0], pool2, &diag), Eq(false)); data = util::Copy(buffers2[0]); test.setTo(data.get(), buffers2[0].size()); EXPECT_THAT(util::GetString(test, 0), "this fits1"); EXPECT_THAT(util::GetString(test, 1), "STRING_TOO_LARGE"); EXPECT_THAT(util::GetString(test, 2), "this fits2"); // Make sure a string that a string that exceeds the maximum length of UTF-8 // but not UTF-16 does not error for UTF-16 StringPool pool3; std::u16string longStr16(50000, 'a'); pool3.MakeRef(longStr); EXPECT_THAT(StringPool::FlattenUtf16(&buffers2[1], pool3, &diag), Eq(true)); data = util::Copy(buffers2[1]); test.setTo(data.get(), buffers2[1].size()); EXPECT_THAT(util::GetString16(test, 0), Eq(longStr16)); } } // namespace aapt tools/aapt2/cmd/Compile.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -258,7 +258,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, ContainerWriter container_writer(©ing_adaptor, 1u); pb::ResourceTable pb_table; SerializeTableToPb(table, &pb_table); SerializeTableToPb(table, &pb_table, context->GetDiagnostics()); if (!container_writer.AddResTableEntry(pb_table)) { context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write"); return false; Loading Loading
tools/aapt2/LoadedApk.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -225,7 +225,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table } } else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) { pb::ResourceTable pb_table; SerializeTableToPb(*split_table, &pb_table); SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics()); if (!io::CopyProtoToArchive(context, &pb_table, path, Loading
tools/aapt2/StringPool.cpp +78 −29 Original line number Diff line number Diff line Loading @@ -332,6 +332,25 @@ static T* EncodeLength(T* data, size_t length) { return data; } /** * Returns the maximum possible string length that can be successfully encoded * using 2 units of the specified T. * EncodeLengthMax<char> -> maximum unit length of 0x7FFF * EncodeLengthMax<char16_t> -> maximum unit length of 0x7FFFFFFF **/ template <typename T> static size_t EncodeLengthMax() { static_assert(std::is_integral<T>::value, "wat."); constexpr size_t kMask = 1 << ((sizeof(T) * 8 * 2) - 1); constexpr size_t max = kMask - 1; return max; } /** * Returns the number of units (1 or 2) needed to encode the string length * before writing the string. */ template <typename T> static size_t EncodedLengthUnits(size_t length) { static_assert(std::is_integral<T>::value, "wat."); Loading @@ -341,15 +360,30 @@ static size_t EncodedLengthUnits(size_t length) { return length > kMaxSize ? 2 : 1; } static void EncodeString(const std::string& str, const bool utf8, BigBuffer* out) { const std::string kStringTooLarge = "STRING_TOO_LARGE"; static bool EncodeString(const std::string& str, const bool utf8, BigBuffer* out, IDiagnostics* diag) { if (utf8) { const std::string& encoded = str; const ssize_t utf16_length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size()); const ssize_t utf16_length = utf8_to_utf16_length( reinterpret_cast<const uint8_t*>(encoded.data()), encoded.size()); CHECK(utf16_length >= 0); const size_t total_size = EncodedLengthUnits<char>(utf16_length) + EncodedLengthUnits<char>(encoded.length()) + encoded.size() + 1; // Make sure the lengths to be encoded do not exceed the maximum length that // can be encoded using chars if ((((size_t)encoded.size()) > EncodeLengthMax<char>()) || (((size_t)utf16_length) > EncodeLengthMax<char>())) { diag->Error(DiagMessage() << "string too large to encode using UTF-8 " << "written instead as '" << kStringTooLarge << "'"); EncodeString(kStringTooLarge, utf8, out, diag); return false; } const size_t total_size = EncodedLengthUnits<char>(utf16_length) + EncodedLengthUnits<char>(encoded.size()) + encoded.size() + 1; char* data = out->NextBlock<char>(total_size); Loading @@ -357,15 +391,26 @@ static void EncodeString(const std::string& str, const bool utf8, BigBuffer* out data = EncodeLength(data, utf16_length); // Now encode the size of the real UTF8 string. data = EncodeLength(data, encoded.length()); data = EncodeLength(data, encoded.size()); strncpy(data, encoded.data(), encoded.size()); } else { const std::u16string encoded = util::Utf8ToUtf16(str); const ssize_t utf16_length = encoded.size(); // Make sure the length to be encoded does not exceed the maximum possible // length that can be encoded if (((size_t)utf16_length) > EncodeLengthMax<char16_t>()) { diag->Error(DiagMessage() << "string too large to encode using UTF-16 " << "written instead as '" << kStringTooLarge << "'"); EncodeString(kStringTooLarge, utf8, out, diag); return false; } // Total number of 16-bit words to write. const size_t total_size = EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1; const size_t total_size = EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1; char16_t* data = out->NextBlock<char16_t>(total_size); Loading @@ -380,9 +425,13 @@ static void EncodeString(const std::string& str, const bool utf8, BigBuffer* out // The null-terminating character is already here due to the block of data // being set to 0s on allocation. } return true; } bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag) { bool no_error = true; const size_t start_index = out->size(); android::ResStringPool_header* header = out->NextBlock<android::ResStringPool_header>(); header->header.type = util::HostToDevice16(android::RES_STRING_POOL_TYPE); Loading @@ -403,12 +452,12 @@ bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { // Styles always come first. for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) { *indices++ = out->size() - before_strings_index; EncodeString(entry->value, utf8, out); no_error = EncodeString(entry->value, utf8, out, diag) && no_error; } for (const std::unique_ptr<Entry>& entry : pool.strings_) { *indices++ = out->size() - before_strings_index; EncodeString(entry->value, utf8, out); no_error = EncodeString(entry->value, utf8, out, diag) && no_error; } out->Align4(); Loading Loading @@ -446,15 +495,15 @@ bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { out->Align4(); } header->header.size = util::HostToDevice32(out->size() - start_index); return true; return no_error; } bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool) { return Flatten(out, pool, true); bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) { return Flatten(out, pool, true, diag); } bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool) { return Flatten(out, pool, false); bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) { return Flatten(out, pool, false, diag); } } // namespace aapt
tools/aapt2/StringPool.h +4 −3 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ #include "androidfw/StringPiece.h" #include "ConfigDescription.h" #include "Diagnostics.h" #include "util/BigBuffer.h" namespace aapt { Loading Loading @@ -152,8 +153,8 @@ class StringPool { int ref_; }; static bool FlattenUtf8(BigBuffer* out, const StringPool& pool); static bool FlattenUtf16(BigBuffer* out, const StringPool& pool); static bool FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag); static bool FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag); StringPool() = default; StringPool(StringPool&&) = default; Loading Loading @@ -207,7 +208,7 @@ class StringPool { private: DISALLOW_COPY_AND_ASSIGN(StringPool); static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8); static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag); Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique); void ReAssignIndices(); Loading
tools/aapt2/StringPool_test.cpp +57 −4 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include "androidfw/StringPiece.h" #include "Diagnostics.h" #include "test/Test.h" #include "util/Util.h" Loading Loading @@ -188,10 +189,11 @@ TEST(StringPoolTest, StylesAndStringsAreSeparateAfterSorting) { TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { using namespace android; // For NO_ERROR on Windows. StdErrDiagnostics diag; StringPool pool; BigBuffer buffer(1024); StringPool::FlattenUtf8(&buffer, pool); StringPool::FlattenUtf8(&buffer, pool, &diag); std::unique_ptr<uint8_t[]> data = util::Copy(buffer); ResStringPool test; Loading @@ -200,11 +202,12 @@ TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { TEST(StringPoolTest, FlattenOddCharactersUtf16) { using namespace android; // For NO_ERROR on Windows. StdErrDiagnostics diag; StringPool pool; pool.MakeRef("\u093f"); BigBuffer buffer(1024); StringPool::FlattenUtf16(&buffer, pool); StringPool::FlattenUtf16(&buffer, pool, &diag); std::unique_ptr<uint8_t[]> data = util::Copy(buffer); ResStringPool test; Loading @@ -225,6 +228,7 @@ constexpr const char* sLongString = TEST(StringPoolTest, Flatten) { using namespace android; // For NO_ERROR on Windows. StdErrDiagnostics diag; StringPool pool; Loading @@ -244,8 +248,8 @@ TEST(StringPoolTest, Flatten) { EXPECT_THAT(ref_d.index(), Eq(4u)); BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; StringPool::FlattenUtf8(&buffers[0], pool); StringPool::FlattenUtf16(&buffers[1], pool); StringPool::FlattenUtf8(&buffers[0], pool, &diag); StringPool::FlattenUtf16(&buffers[1], pool, &diag); // Test both UTF-8 and UTF-16 buffers. for (const BigBuffer& buffer : buffers) { Loading Loading @@ -288,4 +292,53 @@ TEST(StringPoolTest, Flatten) { } } TEST(StringPoolTest, MaxEncodingLength) { StdErrDiagnostics diag; using namespace android; // For NO_ERROR on Windows. ResStringPool test; StringPool pool; pool.MakeRef("aaaaaaaaaa"); BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; // Make sure a UTF-8 string under the maximum length does not produce an error EXPECT_THAT(StringPool::FlattenUtf8(&buffers[0], pool, &diag), Eq(true)); std::unique_ptr<uint8_t[]> data = util::Copy(buffers[0]); test.setTo(data.get(), buffers[0].size()); EXPECT_THAT(util::GetString(test, 0), Eq("aaaaaaaaaa")); // Make sure a UTF-16 string under the maximum length does not produce an error EXPECT_THAT(StringPool::FlattenUtf16(&buffers[1], pool, &diag), Eq(true)); data = util::Copy(buffers[1]); test.setTo(data.get(), buffers[1].size()); EXPECT_THAT(util::GetString16(test, 0), Eq(u"aaaaaaaaaa")); StringPool pool2; std::string longStr(50000, 'a'); pool2.MakeRef("this fits1"); pool2.MakeRef(longStr); pool2.MakeRef("this fits2"); BigBuffer buffers2[2] = {BigBuffer(1024), BigBuffer(1024)}; // Make sure a string that exceeds the maximum length of UTF-8 produces an // error and writes a shorter error string instead EXPECT_THAT(StringPool::FlattenUtf8(&buffers2[0], pool2, &diag), Eq(false)); data = util::Copy(buffers2[0]); test.setTo(data.get(), buffers2[0].size()); EXPECT_THAT(util::GetString(test, 0), "this fits1"); EXPECT_THAT(util::GetString(test, 1), "STRING_TOO_LARGE"); EXPECT_THAT(util::GetString(test, 2), "this fits2"); // Make sure a string that a string that exceeds the maximum length of UTF-8 // but not UTF-16 does not error for UTF-16 StringPool pool3; std::u16string longStr16(50000, 'a'); pool3.MakeRef(longStr); EXPECT_THAT(StringPool::FlattenUtf16(&buffers2[1], pool3, &diag), Eq(true)); data = util::Copy(buffers2[1]); test.setTo(data.get(), buffers2[1].size()); EXPECT_THAT(util::GetString16(test, 0), Eq(longStr16)); } } // namespace aapt
tools/aapt2/cmd/Compile.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -258,7 +258,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, ContainerWriter container_writer(©ing_adaptor, 1u); pb::ResourceTable pb_table; SerializeTableToPb(table, &pb_table); SerializeTableToPb(table, &pb_table, context->GetDiagnostics()); if (!container_writer.AddResTableEntry(pb_table)) { context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write"); return false; Loading