Loading libs/androidfw/include/androidfw/ResourceTypes.h +5 −0 Original line number Diff line number Diff line Loading @@ -1379,6 +1379,11 @@ struct ResTable_typeSpec enum : uint32_t { // Additional flag indicating an entry is public. SPEC_PUBLIC = 0x40000000u, // Additional flag indicating the resource id for this resource may change in a future // build. If this flag is set, the SPEC_PUBLIC flag is also set since the resource must be // public to be exposed as an API to other applications. SPEC_STAGED_API = 0x20000000u, }; }; Loading tools/aapt2/ResourceParser.cpp +65 −47 Original line number Diff line number Diff line Loading @@ -42,6 +42,10 @@ using ::android::StringPiece; using android::idmap2::policy::kPolicyStringToFlag; namespace aapt { namespace { constexpr const char* kPublicGroupTag = "public-group"; constexpr const char* kStagingPublicGroupTag = "staging-public-group"; } // namespace constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2"; Loading Loading @@ -102,6 +106,7 @@ struct ParsedResource { ResourceId id; Visibility::Level visibility_level = Visibility::Level::kUndefined; bool staged_api = false; bool allow_new = false; Maybe<OverlayableItem> overlayable_item; Loading @@ -122,6 +127,7 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed if (res->visibility_level != Visibility::Level::kUndefined) { Visibility visibility; visibility.level = res->visibility_level; visibility.staged_api = res->staged_api; visibility.source = res->source; visibility.comment = res->comment; res_builder.SetVisibility(visibility); Loading Loading @@ -525,6 +531,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, {"plurals", std::mem_fn(&ResourceParser::ParsePlural)}, {"public", std::mem_fn(&ResourceParser::ParsePublic)}, {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)}, {"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)}, {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)}, {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle, std::placeholders::_2, std::placeholders::_3)}, Loading Loading @@ -653,7 +660,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, const auto bag_iter = elToBagMap.find(resource_type); if (bag_iter != elToBagMap.end()) { // Ensure we have a name (unless this is a <public-group> or <overlayable>). if (resource_type != "public-group" && resource_type != "overlayable") { if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag && resource_type != "overlayable") { if (!maybe_name) { diag_->Error(DiagMessage(out_resource->source) << "<" << parser->element_name() << "> missing 'name' attribute"); Loading Loading @@ -890,54 +898,45 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out return true; } bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (options_.visibility) { diag_->Error(DiagMessage(out_resource->source) << "<public-group> tag not allowed with --visibility flag"); return false; } template <typename Func> bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, const char* tag_name, IDiagnostics* diag, Func&& func) { if (out_resource->config != ConfigDescription::DefaultConfig()) { diag_->Warn(DiagMessage(out_resource->source) << "ignoring configuration '" << out_resource->config << "' for <public-group> tag"); diag->Warn(DiagMessage(out_resource->source) << "ignoring configuration '" << out_resource->config << "' for <" << tag_name << "> tag"); } Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { diag_->Error(DiagMessage(out_resource->source) << "<public-group> must have a 'type' attribute"); diag->Error(DiagMessage(out_resource->source) << "<" << tag_name << "> must have a 'type' attribute"); return false; } const ResourceType* parsed_type = ParseResourceType(maybe_type.value()); if (!parsed_type) { diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '" << maybe_type.value() << "' in <public-group>"); diag->Error(DiagMessage(out_resource->source) << "invalid resource type '" << maybe_type.value() << "' in <" << tag_name << ">"); return false; } Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id"); Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id"); if (!maybe_id_str) { diag_->Error(DiagMessage(out_resource->source) << "<public-group> must have a 'first-id' attribute"); diag->Error(DiagMessage(out_resource->source) << "<" << tag_name << "> must have a 'first-id' attribute"); return false; } Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); if (!maybe_id) { diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '" << maybe_id_str.value() << "' in <public-group>"); diag->Error(DiagMessage(out_resource->source) << "invalid resource ID '" << maybe_id_str.value() << "' in <" << tag_name << ">"); return false; } ResourceId next_id = maybe_id.value(); std::string comment; ResourceId next_id = maybe_id.value(); bool error = false; const size_t depth = parser->depth(); while (xml::XmlPullParser::NextChildNode(parser, depth)) { Loading @@ -949,53 +948,72 @@ bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource continue; } const Source item_source = source_.WithLine(parser->line_number()); const Source item_source = out_resource->source.WithLine(parser->line_number()); const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "public") { Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); auto maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (!maybe_name) { diag_->Error(DiagMessage(item_source) << "<public> must have a 'name' attribute"); diag->Error(DiagMessage(item_source) << "<public> must have a 'name' attribute"); error = true; continue; } if (xml::FindNonEmptyAttribute(parser, "id")) { diag_->Error(DiagMessage(item_source) << "'id' is ignored within <public-group>"); diag->Error(DiagMessage(item_source) << "'id' is ignored within <" << tag_name << ">"); error = true; continue; } if (xml::FindNonEmptyAttribute(parser, "type")) { diag_->Error(DiagMessage(item_source) << "'type' is ignored within <public-group>"); diag->Error(DiagMessage(item_source) << "'type' is ignored within <" << tag_name << ">"); error = true; continue; } ParsedResource child_resource; child_resource.name.type = *parsed_type; child_resource.name.entry = maybe_name.value().to_string(); child_resource.id = next_id; // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment child_resource.comment = std::move(comment); child_resource.source = item_source; child_resource.visibility_level = Visibility::Level::kPublic; out_resource->child_resources.push_back(std::move(child_resource)); ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{ .name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()}, .source = item_source, .id = next_id, .comment = std::move(comment), }); next_id.id += 1; // Execute group specific code. func(entry_res, next_id); next_id.id++; } else if (!ShouldIgnoreElement(element_namespace, element_name)) { diag_->Error(DiagMessage(item_source) << ":" << element_name << ">"); diag->Error(DiagMessage(item_source) << ":" << element_name << ">"); error = true; } } return !error; } bool ResourceParser::ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { return ParseGroupImpl(parser, out_resource, kStagingPublicGroupTag, diag_, [](ParsedResource& parsed_entry, ResourceId id) { parsed_entry.id = id; parsed_entry.staged_api = true; parsed_entry.visibility_level = Visibility::Level::kPublic; }); } bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (options_.visibility) { diag_->Error(DiagMessage(out_resource->source) << "<" << kPublicGroupTag << "> tag not allowed with --visibility flag"); return false; } return ParseGroupImpl(parser, out_resource, kPublicGroupTag, diag_, [](ParsedResource& parsed_entry, ResourceId id) { parsed_entry.id = id; parsed_entry.visibility_level = Visibility::Level::kPublic; }); } bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource) { Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); Loading tools/aapt2/ResourceParser.h +1 −0 Original line number Diff line number Diff line Loading @@ -99,6 +99,7 @@ class ResourceParser { bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource); Loading tools/aapt2/ResourceParser_test.cpp +25 −0 Original line number Diff line number Diff line Loading @@ -840,6 +840,31 @@ TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01010041))); } TEST_F(ResourceParserTest, StagingPublicGroup) { std::string input = R"( <staging-public-group type="attr" first-id="0x01ff0049"> <public name="foo" /> <public name="bar" /> </staging-public-group>)"; ASSERT_TRUE(TestParse(input)); Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("attr/foo")); ASSERT_TRUE(result); ASSERT_TRUE(result.value().entry->id); EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff0049))); EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); EXPECT_TRUE(result.value().entry->visibility.staged_api); result = table_.FindResource(test::ParseNameOrDie("attr/bar")); ASSERT_TRUE(result); ASSERT_TRUE(result.value().entry->id); EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff004a))); EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); EXPECT_TRUE(result.value().entry->visibility.staged_api); } TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) { std::string input = R"( <!-- private --> Loading tools/aapt2/ResourceTable.cpp +98 −41 Original line number Diff line number Diff line Loading @@ -46,11 +46,6 @@ bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, ResourceType return lhs->type < rhs; } template <typename T> bool less_than_type_and_id(const T& lhs, const std::pair<ResourceType, Maybe<uint8_t>>& rhs) { return lhs.id != rhs.second ? lhs.id < rhs.second : lhs.type < rhs.first; } template <typename T> bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) { return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; Loading Loading @@ -80,12 +75,6 @@ bool less_than_struct_with_name_and_id(const T& lhs, return lhs.name.compare(0, lhs.name.size(), rhs.first.data(), rhs.first.size()) < 0; } template <typename T, typename U> bool less_than_struct_with_name_and_id_pointer(const T* lhs, const std::pair<std::string_view, Maybe<U>>& rhs) { return less_than_struct_with_name_and_id(*lhs, rhs); } template <typename T, typename Func, typename Elements> T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) { const auto iter = Loading Loading @@ -307,49 +296,113 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist return CollisionResult::kConflict; } template <typename T, typename Comparer> struct SortedVectorInserter : public Comparer { std::pair<bool, typename std::vector<T>::iterator> LowerBound(std::vector<T>& el, const T& value) { auto it = std::lower_bound(el.begin(), el.end(), value, [&](auto& lhs, auto& rhs) { return Comparer::operator()(lhs, rhs); }); bool found = it != el.end() && !Comparer::operator()(*it, value) && !Comparer::operator()(value, *it); return std::make_pair(found, it); } T* Insert(std::vector<T>& el, T&& value) { auto [found, it] = LowerBound(el, value); if (found) { return &*it; } return &*el.insert(it, std::move(value)); } }; struct PackageViewComparer { bool operator()(const ResourceTablePackageView& lhs, const ResourceTablePackageView& rhs) { return less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>( lhs, std::make_pair(rhs.name, rhs.id)); } }; struct TypeViewComparer { bool operator()(const ResourceTableTypeView& lhs, const ResourceTableTypeView& rhs) { return lhs.id != rhs.id ? lhs.id < rhs.id : lhs.type < rhs.type; } }; struct EntryViewComparer { bool operator()(const ResourceEntry* lhs, const ResourceEntry* rhs) { return less_than_struct_with_name_and_id<ResourceEntry, ResourceId>( *lhs, std::make_pair(rhs->name, rhs->id)); } }; ResourceTableView ResourceTable::GetPartitionedView() const { ResourceTableView view; SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter; SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter; SortedVectorInserter<const ResourceEntry*, EntryViewComparer> entry_inserter; for (const auto& package : packages) { for (const auto& type : package->types) { for (const auto& entry : type->entries) { std::pair<std::string_view, Maybe<uint8_t>> package_key(package->name, {}); std::pair<std::string_view, Maybe<ResourceId>> entry_key(entry->name, {}); std::pair<ResourceType, Maybe<uint8_t>> type_key(type->type, {}); if (entry->id) { // If the entry has a defined id, use the id to determine insertion position. package_key.second = entry->id.value().package_id(); type_key.second = entry->id.value().type_id(); entry_key.second = entry->id.value(); ResourceTablePackageView new_package{ package->name, entry->id ? entry->id.value().package_id() : Maybe<uint8_t>{}}; auto view_package = package_inserter.Insert(view.packages, std::move(new_package)); ResourceTableTypeView new_type{type->type, entry->id ? entry->id.value().type_id() : Maybe<uint8_t>{}}; auto view_type = type_inserter.Insert(view_package->types, std::move(new_type)); if (entry->visibility.level == Visibility::Level::kPublic) { // Only mark the type visibility level as public, it doesn't care about being private. view_type->visibility_level = Visibility::Level::kPublic; } auto package_it = std::lower_bound(view.packages.begin(), view.packages.end(), package_key, less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>); if (package_it == view.packages.end() || package_it->name != package_key.first || package_it->id != package_key.second) { ResourceTablePackageView new_package{std::string(package_key.first), package_key.second}; package_it = view.packages.insert(package_it, new_package); entry_inserter.Insert(view_type->entries, entry.get()); } } } auto type_it = std::lower_bound(package_it->types.begin(), package_it->types.end(), type_key, less_than_type_and_id<ResourceTableTypeView>); if (type_it == package_it->types.end() || type_key.first != type_it->type || type_it->id != type_key.second) { ResourceTableTypeView new_type{type_key.first, type_key.second}; type_it = package_it->types.insert(type_it, new_type); // The android runtime does not support querying resources when the there are multiple type ids // for the same resource type within the same package. For this reason, if there are types with // multiple type ids, each type needs to exist in its own package in order to be queried by name. std::vector<ResourceTablePackageView> new_packages; for (auto& package : view.packages) { // If a new package was already created for a different type within this package, then // we can reuse those packages for other types that need to be extracted from this package. // `start_index` is the index of the first newly created package that can be reused. const size_t start_index = new_packages.size(); std::map<ResourceType, size_t> type_new_package_index; for (auto type_it = package.types.begin(); type_it != package.types.end();) { auto& type = *type_it; auto type_index_iter = type_new_package_index.find(type.type); if (type_index_iter == type_new_package_index.end()) { // First occurrence of the resource type in this package. Keep it in this package. type_new_package_index.insert(type_index_iter, std::make_pair(type.type, start_index)); ++type_it; continue; } if (entry->visibility.level == Visibility::Level::kPublic) { // Only mark the type visibility level as public, it doesn't care about being private. type_it->visibility_level = Visibility::Level::kPublic; // The resource type has already been seen for this package, so this type must be extracted to // a new separate package. const size_t index = type_index_iter->second; if (new_packages.size() == index) { new_packages.emplace_back(ResourceTablePackageView{package.name, package.id}); type_new_package_index[type.type] = index + 1; } auto entry_it = std::lower_bound(type_it->entries.begin(), type_it->entries.end(), entry_key, less_than_struct_with_name_and_id_pointer<ResourceEntry, ResourceId>); type_it->entries.insert(entry_it, entry.get()); // Move the type into a new package auto& other_package = new_packages[index]; type_inserter.Insert(other_package.types, std::move(type)); type_it = package.types.erase(type_it); } } for (auto& new_package : new_packages) { // Insert newly created packages after their original packages auto [_, it] = package_inserter.LowerBound(view.packages, new_package); view.packages.insert(++it, std::move(new_package)); } return view; Loading Loading @@ -424,6 +477,10 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { // This symbol definition takes precedence, replace. entry->visibility = res.visibility.value(); } if (res.visibility->staged_api) { entry->visibility.staged_api = entry->visibility.staged_api; } } if (res.overlayable.has_value()) { Loading Loading
libs/androidfw/include/androidfw/ResourceTypes.h +5 −0 Original line number Diff line number Diff line Loading @@ -1379,6 +1379,11 @@ struct ResTable_typeSpec enum : uint32_t { // Additional flag indicating an entry is public. SPEC_PUBLIC = 0x40000000u, // Additional flag indicating the resource id for this resource may change in a future // build. If this flag is set, the SPEC_PUBLIC flag is also set since the resource must be // public to be exposed as an API to other applications. SPEC_STAGED_API = 0x20000000u, }; }; Loading
tools/aapt2/ResourceParser.cpp +65 −47 Original line number Diff line number Diff line Loading @@ -42,6 +42,10 @@ using ::android::StringPiece; using android::idmap2::policy::kPolicyStringToFlag; namespace aapt { namespace { constexpr const char* kPublicGroupTag = "public-group"; constexpr const char* kStagingPublicGroupTag = "staging-public-group"; } // namespace constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2"; Loading Loading @@ -102,6 +106,7 @@ struct ParsedResource { ResourceId id; Visibility::Level visibility_level = Visibility::Level::kUndefined; bool staged_api = false; bool allow_new = false; Maybe<OverlayableItem> overlayable_item; Loading @@ -122,6 +127,7 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed if (res->visibility_level != Visibility::Level::kUndefined) { Visibility visibility; visibility.level = res->visibility_level; visibility.staged_api = res->staged_api; visibility.source = res->source; visibility.comment = res->comment; res_builder.SetVisibility(visibility); Loading Loading @@ -525,6 +531,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, {"plurals", std::mem_fn(&ResourceParser::ParsePlural)}, {"public", std::mem_fn(&ResourceParser::ParsePublic)}, {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)}, {"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)}, {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)}, {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle, std::placeholders::_2, std::placeholders::_3)}, Loading Loading @@ -653,7 +660,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, const auto bag_iter = elToBagMap.find(resource_type); if (bag_iter != elToBagMap.end()) { // Ensure we have a name (unless this is a <public-group> or <overlayable>). if (resource_type != "public-group" && resource_type != "overlayable") { if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag && resource_type != "overlayable") { if (!maybe_name) { diag_->Error(DiagMessage(out_resource->source) << "<" << parser->element_name() << "> missing 'name' attribute"); Loading Loading @@ -890,54 +898,45 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out return true; } bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (options_.visibility) { diag_->Error(DiagMessage(out_resource->source) << "<public-group> tag not allowed with --visibility flag"); return false; } template <typename Func> bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, const char* tag_name, IDiagnostics* diag, Func&& func) { if (out_resource->config != ConfigDescription::DefaultConfig()) { diag_->Warn(DiagMessage(out_resource->source) << "ignoring configuration '" << out_resource->config << "' for <public-group> tag"); diag->Warn(DiagMessage(out_resource->source) << "ignoring configuration '" << out_resource->config << "' for <" << tag_name << "> tag"); } Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { diag_->Error(DiagMessage(out_resource->source) << "<public-group> must have a 'type' attribute"); diag->Error(DiagMessage(out_resource->source) << "<" << tag_name << "> must have a 'type' attribute"); return false; } const ResourceType* parsed_type = ParseResourceType(maybe_type.value()); if (!parsed_type) { diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '" << maybe_type.value() << "' in <public-group>"); diag->Error(DiagMessage(out_resource->source) << "invalid resource type '" << maybe_type.value() << "' in <" << tag_name << ">"); return false; } Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id"); Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id"); if (!maybe_id_str) { diag_->Error(DiagMessage(out_resource->source) << "<public-group> must have a 'first-id' attribute"); diag->Error(DiagMessage(out_resource->source) << "<" << tag_name << "> must have a 'first-id' attribute"); return false; } Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); if (!maybe_id) { diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '" << maybe_id_str.value() << "' in <public-group>"); diag->Error(DiagMessage(out_resource->source) << "invalid resource ID '" << maybe_id_str.value() << "' in <" << tag_name << ">"); return false; } ResourceId next_id = maybe_id.value(); std::string comment; ResourceId next_id = maybe_id.value(); bool error = false; const size_t depth = parser->depth(); while (xml::XmlPullParser::NextChildNode(parser, depth)) { Loading @@ -949,53 +948,72 @@ bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource continue; } const Source item_source = source_.WithLine(parser->line_number()); const Source item_source = out_resource->source.WithLine(parser->line_number()); const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "public") { Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); auto maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (!maybe_name) { diag_->Error(DiagMessage(item_source) << "<public> must have a 'name' attribute"); diag->Error(DiagMessage(item_source) << "<public> must have a 'name' attribute"); error = true; continue; } if (xml::FindNonEmptyAttribute(parser, "id")) { diag_->Error(DiagMessage(item_source) << "'id' is ignored within <public-group>"); diag->Error(DiagMessage(item_source) << "'id' is ignored within <" << tag_name << ">"); error = true; continue; } if (xml::FindNonEmptyAttribute(parser, "type")) { diag_->Error(DiagMessage(item_source) << "'type' is ignored within <public-group>"); diag->Error(DiagMessage(item_source) << "'type' is ignored within <" << tag_name << ">"); error = true; continue; } ParsedResource child_resource; child_resource.name.type = *parsed_type; child_resource.name.entry = maybe_name.value().to_string(); child_resource.id = next_id; // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment child_resource.comment = std::move(comment); child_resource.source = item_source; child_resource.visibility_level = Visibility::Level::kPublic; out_resource->child_resources.push_back(std::move(child_resource)); ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{ .name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()}, .source = item_source, .id = next_id, .comment = std::move(comment), }); next_id.id += 1; // Execute group specific code. func(entry_res, next_id); next_id.id++; } else if (!ShouldIgnoreElement(element_namespace, element_name)) { diag_->Error(DiagMessage(item_source) << ":" << element_name << ">"); diag->Error(DiagMessage(item_source) << ":" << element_name << ">"); error = true; } } return !error; } bool ResourceParser::ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { return ParseGroupImpl(parser, out_resource, kStagingPublicGroupTag, diag_, [](ParsedResource& parsed_entry, ResourceId id) { parsed_entry.id = id; parsed_entry.staged_api = true; parsed_entry.visibility_level = Visibility::Level::kPublic; }); } bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (options_.visibility) { diag_->Error(DiagMessage(out_resource->source) << "<" << kPublicGroupTag << "> tag not allowed with --visibility flag"); return false; } return ParseGroupImpl(parser, out_resource, kPublicGroupTag, diag_, [](ParsedResource& parsed_entry, ResourceId id) { parsed_entry.id = id; parsed_entry.visibility_level = Visibility::Level::kPublic; }); } bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource) { Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); Loading
tools/aapt2/ResourceParser.h +1 −0 Original line number Diff line number Diff line Loading @@ -99,6 +99,7 @@ class ResourceParser { bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource); Loading
tools/aapt2/ResourceParser_test.cpp +25 −0 Original line number Diff line number Diff line Loading @@ -840,6 +840,31 @@ TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01010041))); } TEST_F(ResourceParserTest, StagingPublicGroup) { std::string input = R"( <staging-public-group type="attr" first-id="0x01ff0049"> <public name="foo" /> <public name="bar" /> </staging-public-group>)"; ASSERT_TRUE(TestParse(input)); Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("attr/foo")); ASSERT_TRUE(result); ASSERT_TRUE(result.value().entry->id); EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff0049))); EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); EXPECT_TRUE(result.value().entry->visibility.staged_api); result = table_.FindResource(test::ParseNameOrDie("attr/bar")); ASSERT_TRUE(result); ASSERT_TRUE(result.value().entry->id); EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff004a))); EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); EXPECT_TRUE(result.value().entry->visibility.staged_api); } TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) { std::string input = R"( <!-- private --> Loading
tools/aapt2/ResourceTable.cpp +98 −41 Original line number Diff line number Diff line Loading @@ -46,11 +46,6 @@ bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, ResourceType return lhs->type < rhs; } template <typename T> bool less_than_type_and_id(const T& lhs, const std::pair<ResourceType, Maybe<uint8_t>>& rhs) { return lhs.id != rhs.second ? lhs.id < rhs.second : lhs.type < rhs.first; } template <typename T> bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) { return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; Loading Loading @@ -80,12 +75,6 @@ bool less_than_struct_with_name_and_id(const T& lhs, return lhs.name.compare(0, lhs.name.size(), rhs.first.data(), rhs.first.size()) < 0; } template <typename T, typename U> bool less_than_struct_with_name_and_id_pointer(const T* lhs, const std::pair<std::string_view, Maybe<U>>& rhs) { return less_than_struct_with_name_and_id(*lhs, rhs); } template <typename T, typename Func, typename Elements> T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) { const auto iter = Loading Loading @@ -307,49 +296,113 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist return CollisionResult::kConflict; } template <typename T, typename Comparer> struct SortedVectorInserter : public Comparer { std::pair<bool, typename std::vector<T>::iterator> LowerBound(std::vector<T>& el, const T& value) { auto it = std::lower_bound(el.begin(), el.end(), value, [&](auto& lhs, auto& rhs) { return Comparer::operator()(lhs, rhs); }); bool found = it != el.end() && !Comparer::operator()(*it, value) && !Comparer::operator()(value, *it); return std::make_pair(found, it); } T* Insert(std::vector<T>& el, T&& value) { auto [found, it] = LowerBound(el, value); if (found) { return &*it; } return &*el.insert(it, std::move(value)); } }; struct PackageViewComparer { bool operator()(const ResourceTablePackageView& lhs, const ResourceTablePackageView& rhs) { return less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>( lhs, std::make_pair(rhs.name, rhs.id)); } }; struct TypeViewComparer { bool operator()(const ResourceTableTypeView& lhs, const ResourceTableTypeView& rhs) { return lhs.id != rhs.id ? lhs.id < rhs.id : lhs.type < rhs.type; } }; struct EntryViewComparer { bool operator()(const ResourceEntry* lhs, const ResourceEntry* rhs) { return less_than_struct_with_name_and_id<ResourceEntry, ResourceId>( *lhs, std::make_pair(rhs->name, rhs->id)); } }; ResourceTableView ResourceTable::GetPartitionedView() const { ResourceTableView view; SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter; SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter; SortedVectorInserter<const ResourceEntry*, EntryViewComparer> entry_inserter; for (const auto& package : packages) { for (const auto& type : package->types) { for (const auto& entry : type->entries) { std::pair<std::string_view, Maybe<uint8_t>> package_key(package->name, {}); std::pair<std::string_view, Maybe<ResourceId>> entry_key(entry->name, {}); std::pair<ResourceType, Maybe<uint8_t>> type_key(type->type, {}); if (entry->id) { // If the entry has a defined id, use the id to determine insertion position. package_key.second = entry->id.value().package_id(); type_key.second = entry->id.value().type_id(); entry_key.second = entry->id.value(); ResourceTablePackageView new_package{ package->name, entry->id ? entry->id.value().package_id() : Maybe<uint8_t>{}}; auto view_package = package_inserter.Insert(view.packages, std::move(new_package)); ResourceTableTypeView new_type{type->type, entry->id ? entry->id.value().type_id() : Maybe<uint8_t>{}}; auto view_type = type_inserter.Insert(view_package->types, std::move(new_type)); if (entry->visibility.level == Visibility::Level::kPublic) { // Only mark the type visibility level as public, it doesn't care about being private. view_type->visibility_level = Visibility::Level::kPublic; } auto package_it = std::lower_bound(view.packages.begin(), view.packages.end(), package_key, less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>); if (package_it == view.packages.end() || package_it->name != package_key.first || package_it->id != package_key.second) { ResourceTablePackageView new_package{std::string(package_key.first), package_key.second}; package_it = view.packages.insert(package_it, new_package); entry_inserter.Insert(view_type->entries, entry.get()); } } } auto type_it = std::lower_bound(package_it->types.begin(), package_it->types.end(), type_key, less_than_type_and_id<ResourceTableTypeView>); if (type_it == package_it->types.end() || type_key.first != type_it->type || type_it->id != type_key.second) { ResourceTableTypeView new_type{type_key.first, type_key.second}; type_it = package_it->types.insert(type_it, new_type); // The android runtime does not support querying resources when the there are multiple type ids // for the same resource type within the same package. For this reason, if there are types with // multiple type ids, each type needs to exist in its own package in order to be queried by name. std::vector<ResourceTablePackageView> new_packages; for (auto& package : view.packages) { // If a new package was already created for a different type within this package, then // we can reuse those packages for other types that need to be extracted from this package. // `start_index` is the index of the first newly created package that can be reused. const size_t start_index = new_packages.size(); std::map<ResourceType, size_t> type_new_package_index; for (auto type_it = package.types.begin(); type_it != package.types.end();) { auto& type = *type_it; auto type_index_iter = type_new_package_index.find(type.type); if (type_index_iter == type_new_package_index.end()) { // First occurrence of the resource type in this package. Keep it in this package. type_new_package_index.insert(type_index_iter, std::make_pair(type.type, start_index)); ++type_it; continue; } if (entry->visibility.level == Visibility::Level::kPublic) { // Only mark the type visibility level as public, it doesn't care about being private. type_it->visibility_level = Visibility::Level::kPublic; // The resource type has already been seen for this package, so this type must be extracted to // a new separate package. const size_t index = type_index_iter->second; if (new_packages.size() == index) { new_packages.emplace_back(ResourceTablePackageView{package.name, package.id}); type_new_package_index[type.type] = index + 1; } auto entry_it = std::lower_bound(type_it->entries.begin(), type_it->entries.end(), entry_key, less_than_struct_with_name_and_id_pointer<ResourceEntry, ResourceId>); type_it->entries.insert(entry_it, entry.get()); // Move the type into a new package auto& other_package = new_packages[index]; type_inserter.Insert(other_package.types, std::move(type)); type_it = package.types.erase(type_it); } } for (auto& new_package : new_packages) { // Insert newly created packages after their original packages auto [_, it] = package_inserter.LowerBound(view.packages, new_package); view.packages.insert(++it, std::move(new_package)); } return view; Loading Loading @@ -424,6 +477,10 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { // This symbol definition takes precedence, replace. entry->visibility = res.visibility.value(); } if (res.visibility->staged_api) { entry->visibility.staged_api = entry->visibility.staged_api; } } if (res.overlayable.has_value()) { Loading