diff options
author | Ryan Mitchell <rtmitchell@google.com> | 2020-11-16 23:08:18 +0000 |
---|---|---|
committer | Ryan Mitchell <rtmitchell@google.com> | 2020-12-08 16:58:12 +0000 |
commit | 80094e39f90801c44cd80ab0f98df505828ea1f3 (patch) | |
tree | e70d5241691a509ce9cf774dc39ce85932fdd1d2 /tools | |
parent | ec7e7f5622e3444a3003db20ddfd8f5745971fa7 (diff) |
Revert^2 "libandroidfw hardening for IncFs"
55ef6167a2c235bd88c7216238b2001b46795b79
Change-Id: I02d4890d181655dfd0a14c188468db512559d27b
Merged-In: I02d4890d181655dfd0a14c188468db512559d27b
Diffstat (limited to 'tools')
-rw-r--r-- | tools/aapt/Resource.cpp | 2 | ||||
-rw-r--r-- | tools/aapt/ResourceTable.cpp | 4 | ||||
-rw-r--r-- | tools/aapt/StringPool.cpp | 8 | ||||
-rw-r--r-- | tools/aapt2/Debug.cpp | 8 | ||||
-rw-r--r-- | tools/aapt2/ResourceUtils.cpp | 6 | ||||
-rw-r--r-- | tools/aapt2/StringPool_test.cpp | 40 | ||||
-rw-r--r-- | tools/aapt2/cmd/Compile_test.cpp | 1 | ||||
-rw-r--r-- | tools/aapt2/cmd/Link.cpp | 24 | ||||
-rw-r--r-- | tools/aapt2/format/binary/TableFlattener_test.cpp | 30 | ||||
-rw-r--r-- | tools/aapt2/process/SymbolTable.cpp | 51 | ||||
-rw-r--r-- | tools/aapt2/util/Util.cpp | 12 | ||||
-rw-r--r-- | tools/split-select/Main.cpp | 18 |
12 files changed, 103 insertions, 101 deletions
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index ab6dced5b67d..dd3ebdbdea09 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -519,7 +519,7 @@ static int validateAttr(const String8& path, const ResTable& table, String8(parser.getElementName(&len)).string(), attr); return ATTR_NOT_FOUND; } - if ((str=pool->stringAt(value.data, &len)) == NULL) { + if ((str = UnpackOptionalString(pool->stringAt(value.data), &len)) == NULL) { fprintf(stderr, "%s:%d: Tag <%s> attribute %s has corrupt string value.\n", path.string(), parser.getLineNumber(), String8(parser.getElementName(&len)).string(), attr); diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index d02f44edaa4c..257e96b6e51a 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -3066,7 +3066,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& for (size_t ti=0; ti<N; ti++) { // Retrieve them in the same order as the type string block. size_t len; - String16 typeName(p->getTypeStrings().stringAt(ti, &len)); + String16 typeName(UnpackOptionalString(p->getTypeStrings().stringAt(ti), &len)); sp<Type> t = p->getTypes().valueFor(typeName); LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"), "Type name %s not found", @@ -4169,7 +4169,7 @@ status_t ResourceTable::Package::setStrings(const sp<AaptFile>& data, const size_t N = strings->size(); for (size_t i=0; i<N; i++) { size_t len; - mappings->add(String16(strings->stringAt(i, &len)), i); + mappings->add(String16(UnpackOptionalString(strings->stringAt(i), &len)), i); } } return err; diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp index 37b61bfdffbd..6cacd32eb91d 100644 --- a/tools/aapt/StringPool.cpp +++ b/tools/aapt/StringPool.cpp @@ -52,9 +52,9 @@ void printStringPool(const ResStringPool* pool) for (size_t i=0; i<N; i++) { size_t len; if (pool->isUTF8()) { - uniqueStrings.add(pool->string8At(i, &len)); + uniqueStrings.add(UnpackOptionalString(pool->string8At(i), &len)); } else { - uniqueStrings.add(pool->stringAt(i, &len)); + uniqueStrings.add(UnpackOptionalString(pool->stringAt(i), &len)); } } @@ -66,8 +66,8 @@ void printStringPool(const ResStringPool* pool) const size_t NS = pool->size(); for (size_t s=0; s<NS; s++) { - String8 str = pool->string8ObjectAt(s); - printf("String #" ZD ": %s\n", (ZD_TYPE) s, str.string()); + auto str = pool->string8ObjectAt(s); + printf("String #" ZD ": %s\n", (ZD_TYPE) s, (str.has_value() ? str->string() : "")); } } diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 439f231193df..82da24959521 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -436,9 +436,9 @@ void Debug::DumpResStringPool(const android::ResStringPool* pool, text::Printer* for (size_t i=0; i<N; i++) { size_t len; if (pool->isUTF8()) { - uniqueStrings.add(pool->string8At(i, &len)); + uniqueStrings.add(UnpackOptionalString(pool->string8At(i), &len)); } else { - uniqueStrings.add(pool->stringAt(i, &len)); + uniqueStrings.add(UnpackOptionalString(pool->stringAt(i), &len)); } } @@ -450,8 +450,8 @@ void Debug::DumpResStringPool(const android::ResStringPool* pool, text::Printer* const size_t NS = pool->size(); for (size_t s=0; s<NS; s++) { - String8 str = pool->string8ObjectAt(s); - printer->Print(StringPrintf("String #%zd : %s\n", s, str.string())); + auto str = pool->string8ObjectAt(s); + printer->Print(StringPrintf("String #%zd : %s\n", s, str.has_value() ? str->string() : "")); } } diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 469128b1e50b..f26e995aa4f9 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -751,10 +751,12 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config switch (res_value.dataType) { case android::Res_value::TYPE_STRING: { const std::string str = util::GetString(src_pool, data); - const android::ResStringPool_span* spans = src_pool.styleAt(data); + auto spans_result = src_pool.styleAt(data); // Check if the string has a valid style associated with it. - if (spans != nullptr && spans->name.index != android::ResStringPool_span::END) { + if (spans_result.has_value() && + (*spans_result)->name.index != android::ResStringPool_span::END) { + const android::ResStringPool_span* spans = spans_result->unsafe_ptr(); StyleString style_str = {str}; while (spans->name.index != android::ResStringPool_span::END) { style_str.spans.push_back(Span{util::GetString(src_pool, spans->name.index), diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp index 9a7238b584ba..6e5200bca44c 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -223,11 +223,11 @@ TEST(StringPoolTest, FlattenOddCharactersUtf16) { std::unique_ptr<uint8_t[]> data = util::Copy(buffer); ResStringPool test; ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - size_t len = 0; - const char16_t* str = test.stringAt(0, &len); - EXPECT_THAT(len, Eq(1u)); - EXPECT_THAT(str, Pointee(Eq(u'\u093f'))); - EXPECT_THAT(str[1], Eq(0u)); + auto str = test.stringAt(0); + ASSERT_TRUE(str.has_value()); + EXPECT_THAT(str->size(), Eq(1u)); + EXPECT_THAT(str->data(), Pointee(Eq(u'\u093f'))); + EXPECT_THAT(str->data()[1], Eq(0u)); } constexpr const char* sLongString = @@ -278,14 +278,15 @@ TEST(StringPoolTest, Flatten) { EXPECT_THAT(util::GetString(test, 3), Eq(sLongString)); EXPECT_THAT(util::GetString16(test, 3), Eq(util::Utf8ToUtf16(sLongString))); - size_t len; - EXPECT_TRUE(test.stringAt(4, &len) != nullptr || test.string8At(4, &len) != nullptr); + EXPECT_TRUE(test.stringAt(4).has_value() || test.string8At(4).has_value()); EXPECT_THAT(util::GetString(test, 0), Eq("style")); EXPECT_THAT(util::GetString16(test, 0), Eq(u"style")); - const ResStringPool_span* span = test.styleAt(0); - ASSERT_THAT(span, NotNull()); + auto span_result = test.styleAt(0); + ASSERT_TRUE(span_result.has_value()); + + const ResStringPool_span* span = span_result->unsafe_ptr(); EXPECT_THAT(util::GetString(test, span->name.index), Eq("b")); EXPECT_THAT(util::GetString16(test, span->name.index), Eq(u"b")); EXPECT_THAT(span->firstChar, Eq(0u)); @@ -318,16 +319,17 @@ TEST(StringPoolTest, ModifiedUTF8) { // Check that the codepoints are encoded using two three-byte surrogate pairs ResStringPool test; ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - size_t len; - const char* str = test.string8At(0, &len); - ASSERT_THAT(str, NotNull()); - EXPECT_THAT(std::string(str, len), Eq("\xED\xA0\x81\xED\xB0\x80")); - str = test.string8At(1, &len); - ASSERT_THAT(str, NotNull()); - EXPECT_THAT(std::string(str, len), Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar")); - str = test.string8At(2, &len); - ASSERT_THAT(str, NotNull()); - EXPECT_THAT(std::string(str, len), Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7")); + auto str = test.string8At(0); + ASSERT_TRUE(str.has_value()); + EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80")); + + str = test.string8At(1); + ASSERT_TRUE(str.has_value()); + EXPECT_THAT(str->to_string(), Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar")); + + str = test.string8At(2); + ASSERT_TRUE(str.has_value()); + EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7")); // Check that retrieving the strings returns the original UTF-8 character bytes EXPECT_THAT(util::GetString(test, 0), Eq("\xF0\x90\x90\x80")); diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index fb786a31360e..ae9f7926cf09 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -252,5 +252,4 @@ TEST_F(CompilerTest, DoNotTranslateTest) { AssertTranslations(this, "donottranslate", expected_not_translatable); AssertTranslations(this, "donottranslate_foo", expected_not_translatable); } - } // namespace aapt diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index fd12d02434fa..faabe461648a 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -730,22 +730,6 @@ static bool LoadStableIdMap(IDiagnostics* diag, const std::string& path, return true; } -static android::ApkAssetsCookie FindFrameworkAssetManagerCookie( - const android::AssetManager2& assets) { - using namespace android; - - // Find the system package (0x01). AAPT always generates attributes with the type 0x01, so - // we're looking for the first attribute resource in the system package. - Res_value val{}; - ResTable_config config{}; - uint32_t type_spec_flags; - ApkAssetsCookie idx = assets.GetResource(0x01010000, true /** may_be_bag */, - 0 /** density_override */, &val, &config, - &type_spec_flags); - - return idx; -} - class Linker { public: Linker(LinkContext* context, const LinkOptions& options) @@ -758,8 +742,12 @@ class Linker { void ExtractCompileSdkVersions(android::AssetManager2* assets) { using namespace android; - android::ApkAssetsCookie cookie = FindFrameworkAssetManagerCookie(*assets); - if (cookie == android::kInvalidCookie) { + // Find the system package (0x01). AAPT always generates attributes with the type 0x01, so + // we're looking for the first attribute resource in the system package. + android::ApkAssetsCookie cookie; + if (auto value = assets->GetResource(0x01010000, true /** may_be_bag */); value.has_value()) { + cookie = value->cookie; + } else { // No Framework assets loaded. Not a failure. return; } diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index 6932baf76c75..f8b8a1c4e35b 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -189,16 +189,16 @@ TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) { ResTable_config::CONFIG_VERSION)); std::u16string foo_str = u"foo"; - ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); - ASSERT_GE(idx, 0); + auto idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); + ASSERT_TRUE(idx.has_value()); EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {}, - Res_value::TYPE_STRING, (uint32_t)idx, 0u)); + Res_value::TYPE_STRING, (uint32_t)*idx, 0u)); std::u16string bar_path = u"res/layout/bar.xml"; idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); - ASSERT_GE(idx, 0); + ASSERT_TRUE(idx.has_value()); EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar", ResourceId(0x7f050000), {}, - Res_value::TYPE_STRING, (uint32_t)idx, 0u)); + Res_value::TYPE_STRING, (uint32_t)*idx, 0u)); } TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) { @@ -603,16 +603,16 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucce 2u, ResTable_config::CONFIG_VERSION)); std::u16string foo_str = u"foo"; - ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); - ASSERT_GE(idx, 0); + auto idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); + ASSERT_TRUE(idx.has_value()); EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated", - ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); + ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u)); std::u16string bar_path = u"res/layout/bar.xml"; idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); - ASSERT_GE(idx, 0); + ASSERT_TRUE(idx.has_value()); EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated", - ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); + ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u)); } TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) { @@ -659,16 +659,16 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSuc 2u, ResTable_config::CONFIG_VERSION)); std::u16string foo_str = u"foo"; - ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); - ASSERT_GE(idx, 0); + auto idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); + ASSERT_TRUE(idx.has_value()); EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {}, - Res_value::TYPE_STRING, (uint32_t)idx, 0u)); + Res_value::TYPE_STRING, (uint32_t)*idx, 0u)); std::u16string bar_path = u"res/layout/bar.xml"; idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); - ASSERT_GE(idx, 0); + ASSERT_TRUE(idx.has_value()); EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated", - ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); + ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u)); } TEST_F(TableFlattenerTest, FlattenOverlayable) { diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 897fa80ffedb..ad716c78d65d 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -265,21 +265,22 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId, static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( android::AssetManager2& am, ResourceId id) { + using namespace android; if (am.GetApkAssets().empty()) { return {}; } - const android::ResolvedBag* bag = am.GetBag(id.id); - if (bag == nullptr) { + auto bag_result = am.GetBag(id.id); + if (!bag_result.has_value()) { return nullptr; } // We found a resource. std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>(id); - + const ResolvedBag* bag = *bag_result; const size_t count = bag->entry_count; for (uint32_t i = 0; i < count; i++) { - if (bag->entries[i].key == android::ResTable_map::ATTR_TYPE) { + if (bag->entries[i].key == ResTable_map::ATTR_TYPE) { s->attribute = std::make_shared<Attribute>(bag->entries[i].value.data); break; } @@ -287,25 +288,25 @@ static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( if (s->attribute) { for (size_t i = 0; i < count; i++) { - const android::ResolvedBag::Entry& map_entry = bag->entries[i]; + const ResolvedBag::Entry& map_entry = bag->entries[i]; if (Res_INTERNALID(map_entry.key)) { switch (map_entry.key) { - case android::ResTable_map::ATTR_MIN: + case ResTable_map::ATTR_MIN: s->attribute->min_int = static_cast<int32_t>(map_entry.value.data); break; - case android::ResTable_map::ATTR_MAX: + case ResTable_map::ATTR_MAX: s->attribute->max_int = static_cast<int32_t>(map_entry.value.data); break; } continue; } - android::AssetManager2::ResourceName name; - if (!am.GetResourceName(map_entry.key, &name)) { + auto name = am.GetResourceName(map_entry.key); + if (!name.has_value()) { return nullptr; } - Maybe<ResourceName> parsed_name = ResourceUtils::ToResourceName(name); + Maybe<ResourceName> parsed_name = ResourceUtils::ToResourceName(*name); if (!parsed_name) { return nullptr; } @@ -328,7 +329,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( bool found = false; ResourceId res_id = 0; - uint32_t type_spec_flags; + uint32_t type_spec_flags = 0; ResourceName real_name; // There can be mangled resources embedded within other packages. Here we will @@ -340,8 +341,19 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( real_name.package = package_name; } - res_id = asset_manager_.GetResourceId(real_name.to_string()); - if (res_id.is_valid_static() && asset_manager_.GetResourceFlags(res_id.id, &type_spec_flags)) { + auto real_res_id = asset_manager_.GetResourceId(real_name.to_string()); + if (!real_res_id.has_value()) { + return true; + } + + res_id.id = *real_res_id; + if (!res_id.is_valid_static()) { + return true; + } + + auto value = asset_manager_.GetResource(res_id.id, true /* may_be_bag */); + if (value.has_value()) { + type_spec_flags = value->flags; found = true; return false; } @@ -371,11 +383,11 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( static Maybe<ResourceName> GetResourceName(android::AssetManager2& am, ResourceId id) { - android::AssetManager2::ResourceName name; - if (!am.GetResourceName(id.id, &name)) { + auto name = am.GetResourceName(id.id); + if (!name.has_value()) { return {}; } - return ResourceUtils::ToResourceName(name); + return ResourceUtils::ToResourceName(*name); } std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( @@ -394,9 +406,8 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( return {}; } - - uint32_t type_spec_flags = 0; - if (!asset_manager_.GetResourceFlags(id.id, &type_spec_flags)) { + auto value = asset_manager_.GetResource(id.id, true /* may_be_bag */); + if (!value.has_value()) { return {}; } @@ -411,7 +422,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( } if (s) { - s->is_public = (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + s->is_public = (value->flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; return s; } return {}; diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 37ce65e4fe5b..ef33c3463a81 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -531,19 +531,15 @@ bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix, } StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx) { - size_t len; - const char16_t* str = pool.stringAt(idx, &len); - if (str != nullptr) { - return StringPiece16(str, len); + if (auto str = pool.stringAt(idx)) { + return *str; } return StringPiece16(); } std::string GetString(const android::ResStringPool& pool, size_t idx) { - size_t len; - const char* str = pool.string8At(idx, &len); - if (str != nullptr) { - return ModifiedUtf8ToUtf8(std::string(str, len)); + if (auto str = pool.string8At(idx)) { + return ModifiedUtf8ToUtf8(str->to_string()); } return Utf16ToUtf8(GetString16(pool, idx)); } diff --git a/tools/split-select/Main.cpp b/tools/split-select/Main.cpp index d3eb012a80e9..e6966db0aa00 100644 --- a/tools/split-select/Main.cpp +++ b/tools/split-select/Main.cpp @@ -182,14 +182,18 @@ static bool getAppInfo(const String8& path, AppInfo& outInfo) { if (type >= Res_value::TYPE_FIRST_INT && type <= Res_value::TYPE_LAST_INT) { outInfo.minSdkVersion = xml.getAttributeData(idx); } else if (type == Res_value::TYPE_STRING) { - String8 minSdk8(xml.getStrings().string8ObjectAt(idx)); - char* endPtr; - int minSdk = strtol(minSdk8.string(), &endPtr, 10); - if (endPtr != minSdk8.string() + minSdk8.size()) { - fprintf(stderr, "warning: failed to parse android:minSdkVersion '%s'\n", - minSdk8.string()); + auto minSdk8 = xml.getStrings().string8ObjectAt(idx); + if (!minSdk8.has_value()) { + fprintf(stderr, "warning: failed to retrieve android:minSdkVersion.\n"); } else { - outInfo.minSdkVersion = minSdk; + char *endPtr; + int minSdk = strtol(minSdk8->string(), &endPtr, 10); + if (endPtr != minSdk8->string() + minSdk8->size()) { + fprintf(stderr, "warning: failed to parse android:minSdkVersion '%s'\n", + minSdk8->string()); + } else { + outInfo.minSdkVersion = minSdk; + } } } else { fprintf(stderr, "warning: unrecognized value for android:minSdkVersion.\n"); |