diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index b78f48ce7f176..6364ccdd09e5c 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -78,6 +78,8 @@ StringPiece to_string(ResourceType type) { return "interpolator"; case ResourceType::kLayout: return "layout"; + case ResourceType::kMacro: + return "macro"; case ResourceType::kMenu: return "menu"; case ResourceType::kMipmap: @@ -119,6 +121,7 @@ static const std::map sResourceTypeMap{ {"integer", ResourceType::kInteger}, {"interpolator", ResourceType::kInterpolator}, {"layout", ResourceType::kLayout}, + {"macro", ResourceType::kMacro}, {"menu", ResourceType::kMenu}, {"mipmap", ResourceType::kMipmap}, {"navigation", ResourceType::kNavigation}, diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index cf938703e1e99..307c21d9dc965 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -57,6 +57,7 @@ enum class ResourceType { kInteger, kInterpolator, kLayout, + kMacro, kMenu, kMipmap, kNavigation, diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 24c60b740bc35..1efabbb46fd59 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -627,6 +627,16 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, } return true; + } else if (resource_type == "macro") { + if (!maybe_name) { + diag_->Error(DiagMessage(out_resource->source) + << "<" << parser->element_name() << "> missing 'name' attribute"); + return false; + } + + out_resource->name.type = ResourceType::kMacro; + out_resource->name.entry = maybe_name.value().to_string(); + return ParseMacro(parser, out_resource); } if (can_be_item) { @@ -726,16 +736,8 @@ bool ResourceParser::ParseItem(xml::XmlPullParser* parser, return true; } -/** - * Reads the entire XML subtree and attempts to parse it as some Item, - * with typeMask denoting which items it can be. If allowRawValue is - * true, a RawString is returned if the XML couldn't be parsed as - * an Item. If allowRawValue is false, nullptr is returned in this - * case. - */ -std::unique_ptr ResourceParser::ParseXml(xml::XmlPullParser* parser, - const uint32_t type_mask, - const bool allow_raw_value) { +std::optional ResourceParser::CreateFlattenSubTree( + xml::XmlPullParser* parser) { const size_t begin_xml_line = parser->line_number(); std::string raw_value; @@ -745,30 +747,60 @@ std::unique_ptr ResourceParser::ParseXml(xml::XmlPullParser* parser, return {}; } - if (!style_string.spans.empty()) { + return FlattenedXmlSubTree{.raw_value = raw_value, + .style_string = style_string, + .untranslatable_sections = untranslatable_sections, + .namespace_resolver = parser, + .source = source_.WithLine(begin_xml_line)}; +} + +/** + * Reads the entire XML subtree and attempts to parse it as some Item, + * with typeMask denoting which items it can be. If allowRawValue is + * true, a RawString is returned if the XML couldn't be parsed as + * an Item. If allowRawValue is false, nullptr is returned in this + * case. + */ +std::unique_ptr ResourceParser::ParseXml(xml::XmlPullParser* parser, const uint32_t type_mask, + const bool allow_raw_value) { + auto sub_tree = CreateFlattenSubTree(parser); + if (!sub_tree.has_value()) { + return {}; + } + return ParseXml(sub_tree.value(), type_mask, allow_raw_value, *table_, config_, *diag_); +} + +std::unique_ptr ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub_tree, + const uint32_t type_mask, const bool allow_raw_value, + ResourceTable& table, + const android::ConfigDescription& config, + IDiagnostics& diag) { + if (!xmlsub_tree.style_string.spans.empty()) { // This can only be a StyledString. std::unique_ptr styled_string = - util::make_unique(table_->string_pool.MakeRef( - style_string, StringPool::Context(StringPool::Context::kNormalPriority, config_))); - styled_string->untranslatable_sections = std::move(untranslatable_sections); + util::make_unique(table.string_pool.MakeRef( + xmlsub_tree.style_string, + StringPool::Context(StringPool::Context::kNormalPriority, config))); + styled_string->untranslatable_sections = xmlsub_tree.untranslatable_sections; return std::move(styled_string); } auto on_create_reference = [&](const ResourceName& name) { // name.package can be empty here, as it will assume the package name of the // table. - std::unique_ptr id = util::make_unique(); - id->SetSource(source_.WithLine(begin_xml_line)); - table_->AddResource(NewResourceBuilder(name).SetValue(std::move(id)).Build(), diag_); + auto id = util::make_unique(); + id->SetSource(xmlsub_tree.source); + return table.AddResource(NewResourceBuilder(name).SetValue(std::move(id)).Build(), &diag); }; // Process the raw value. - std::unique_ptr processed_item = - ResourceUtils::TryParseItemForAttribute(raw_value, type_mask, on_create_reference); + std::unique_ptr processed_item = ResourceUtils::TryParseItemForAttribute( + xmlsub_tree.raw_value, type_mask, on_create_reference); if (processed_item) { // Fix up the reference. - if (Reference* ref = ValueCast(processed_item.get())) { - ResolvePackage(parser, ref); + if (auto ref = ValueCast(processed_item.get())) { + ref->allow_raw = allow_raw_value; + ResolvePackage(xmlsub_tree.namespace_resolver, ref); } return processed_item; } @@ -777,17 +809,16 @@ std::unique_ptr ResourceParser::ParseXml(xml::XmlPullParser* parser, if (type_mask & android::ResTable_map::TYPE_STRING) { // Use the trimmed, escaped string. std::unique_ptr string = util::make_unique( - table_->string_pool.MakeRef(style_string.str, StringPool::Context(config_))); - string->untranslatable_sections = std::move(untranslatable_sections); + table.string_pool.MakeRef(xmlsub_tree.style_string.str, StringPool::Context(config))); + string->untranslatable_sections = xmlsub_tree.untranslatable_sections; return std::move(string); } if (allow_raw_value) { // We can't parse this so return a RawString if we are allowed. - return util::make_unique( - table_->string_pool.MakeRef(util::TrimWhitespace(raw_value), - StringPool::Context(config_))); - } else if (util::TrimWhitespace(raw_value).empty()) { + return util::make_unique(table.string_pool.MakeRef( + util::TrimWhitespace(xmlsub_tree.raw_value), StringPool::Context(config))); + } else if (util::TrimWhitespace(xmlsub_tree.raw_value).empty()) { // If the text is empty, and the value is not allowed to be a string, encode it as a @null. return ResourceUtils::MakeNull(); } @@ -850,6 +881,35 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser, return true; } +bool ResourceParser::ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_resource) { + auto sub_tree = CreateFlattenSubTree(parser); + if (!sub_tree) { + return false; + } + + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Error(DiagMessage(out_resource->source) + << " tags cannot be declared in configurations other than the default " + "configuration'"); + return false; + } + + auto macro = std::make_unique(); + macro->raw_value = std::move(sub_tree->raw_value); + macro->style_string = std::move(sub_tree->style_string); + macro->untranslatable_sections = std::move(sub_tree->untranslatable_sections); + + for (const auto& decl : parser->package_decls()) { + macro->alias_namespaces.emplace_back( + Macro::Namespace{.alias = decl.prefix, + .package_name = decl.package.package, + .is_private = decl.package.private_namespace}); + } + + out_resource->value = std::move(macro); + return true; +} + bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (options_.visibility) { diag_->Error(DiagMessage(out_resource->source) diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index af0db8c0ba2ca..5c92def50616f 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -57,6 +57,14 @@ struct ResourceParserOptions { Maybe visibility; }; +struct FlattenedXmlSubTree { + std::string raw_value; + StyleString style_string; + std::vector untranslatable_sections; + xml::IPackageDeclStack* namespace_resolver; + Source source; +}; + /* * Parses an XML file for resources and adds them to a ResourceTable. */ @@ -67,9 +75,16 @@ class ResourceParser { const ResourceParserOptions& options = {}); bool Parse(xml::XmlPullParser* parser); + static std::unique_ptr ParseXml(const FlattenedXmlSubTree& xmlsub_tree, uint32_t type_mask, + bool allow_raw_value, ResourceTable& table, + const android::ConfigDescription& config, + IDiagnostics& diag); + private: DISALLOW_COPY_AND_ASSIGN(ResourceParser); + std::optional CreateFlattenSubTree(xml::XmlPullParser* parser); + // Parses the XML subtree as a StyleString (flattened XML representation for strings with // formatting). If parsing fails, false is returned and the out parameters are left in an // unspecified state. Otherwise, @@ -96,7 +111,7 @@ class ResourceParser { bool ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource, uint32_t format); bool ParseString(xml::XmlPullParser* parser, ParsedResource* out_resource); - + bool ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource); @@ -108,8 +123,7 @@ class ResourceParser { bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak); Maybe ParseEnumOrFlagItem(xml::XmlPullParser* parser, const android::StringPiece& tag); - bool ParseStyle(const ResourceType type, xml::XmlPullParser* parser, - ParsedResource* out_resource); + bool ParseStyle(ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseStyleItem(xml::XmlPullParser* parser, Style* style); bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 4a509be567765..279ebcba2f71e 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -336,6 +336,90 @@ TEST_F(ResourceParserTest, ParseAttr) { EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_ANY)); } +TEST_F(ResourceParserTest, ParseMacro) { + std::string input = R"(12345)"; + ASSERT_TRUE(TestParse(input)); + + Macro* macro = test::GetValue(&table_, "macro/foo"); + ASSERT_THAT(macro, NotNull()); + EXPECT_THAT(macro->raw_value, Eq("12345")); + EXPECT_THAT(macro->style_string.str, Eq("12345")); + EXPECT_THAT(macro->style_string.spans, IsEmpty()); + EXPECT_THAT(macro->untranslatable_sections, IsEmpty()); + EXPECT_THAT(macro->alias_namespaces, IsEmpty()); +} + +TEST_F(ResourceParserTest, ParseMacroUntranslatableSection) { + std::string input = R"( +This being human is a guest house.)"; + ASSERT_TRUE(TestParse(input)); + + Macro* macro = test::GetValue(&table_, "macro/foo"); + ASSERT_THAT(macro, NotNull()); + EXPECT_THAT(macro->raw_value, Eq("\nThis being human is a guest house.")); + EXPECT_THAT(macro->style_string.str, Eq(" This being human is a guest house.")); + EXPECT_THAT(macro->style_string.spans.size(), Eq(1)); + EXPECT_THAT(macro->style_string.spans[0].name, Eq("b")); + EXPECT_THAT(macro->style_string.spans[0].first_char, Eq(12)); + EXPECT_THAT(macro->style_string.spans[0].last_char, Eq(16)); + ASSERT_THAT(macro->untranslatable_sections.size(), Eq(1)); + EXPECT_THAT(macro->untranslatable_sections[0].start, Eq(12)); + EXPECT_THAT(macro->untranslatable_sections[0].end, Eq(17)); + EXPECT_THAT(macro->alias_namespaces, IsEmpty()); +} + +TEST_F(ResourceParserTest, ParseMacroNamespaces) { + std::string input = R"( +@app:string/foo)"; + ASSERT_TRUE(TestParse(input)); + + Macro* macro = test::GetValue(&table_, "macro/foo"); + ASSERT_THAT(macro, NotNull()); + EXPECT_THAT(macro->raw_value, Eq("\n@app:string/foo")); + EXPECT_THAT(macro->style_string.str, Eq("@app:string/foo")); + EXPECT_THAT(macro->style_string.spans, IsEmpty()); + EXPECT_THAT(macro->untranslatable_sections, IsEmpty()); + EXPECT_THAT(macro->alias_namespaces.size(), Eq(1)); + EXPECT_THAT(macro->alias_namespaces[0].alias, Eq("app")); + EXPECT_THAT(macro->alias_namespaces[0].package_name, Eq("android")); + EXPECT_THAT(macro->alias_namespaces[0].is_private, Eq(false)); +} + +TEST_F(ResourceParserTest, ParseMacroReference) { + std::string input = R"(@macro/foo)"; + ASSERT_TRUE(TestParse(input)); + + Reference* macro = test::GetValue(&table_, "string/res_string"); + ASSERT_THAT(macro, NotNull()); + EXPECT_THAT(macro->type_flags, Eq(ResTable_map::TYPE_STRING)); + EXPECT_THAT(macro->allow_raw, Eq(false)); + + input = R"()"; + + ASSERT_TRUE(TestParse(input)); + Style* style = test::GetValue + )"; + + const std::string xml_values = + R"( + )"; + + // Build a library with a public attribute + const std::string lib_res = GetTestPath("test-res"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), values, lib_res, &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/layout/layout.xml"), xml_values, lib_res, &diag)); + + const std::string lib_apk = GetTestPath("test.apk"); + // clang-format off + auto lib_link_args = LinkCommandBuilder(this) + .SetManifestFile(ManifestBuilder(this).SetPackageName("com.test").Build()) + .AddCompiledResDir(lib_res, &diag) + .AddFlag("--no-auto-version") + .Build(lib_apk); + // clang-format on + ASSERT_TRUE(Link(lib_link_args, &diag)); + + auto apk = LoadedApk::LoadApkFromPath(lib_apk, &diag); + ASSERT_THAT(apk, NotNull()); + + // Test that the type flags determines the value type + auto actual_bool = + test::GetValue(apk->GetResourceTable(), "com.test:bool/is_enabled_bool"); + ASSERT_THAT(actual_bool, NotNull()); + EXPECT_EQ(android::Res_value::TYPE_INT_BOOLEAN, actual_bool->value.dataType); + EXPECT_EQ(0xffffffffu, actual_bool->value.data); + + auto actual_str = + test::GetValue(apk->GetResourceTable(), "com.test:string/is_enabled_str"); + ASSERT_THAT(actual_str, NotNull()); + EXPECT_EQ(*actual_str->value, "true"); + + // Test nested data structures + auto actual_array = test::GetValue(apk->GetResourceTable(), "com.test:array/my_array"); + ASSERT_THAT(actual_array, NotNull()); + EXPECT_THAT(actual_array->elements.size(), Eq(1)); + + auto array_el_ref = ValueCast(actual_array->elements[0].get()); + ASSERT_THAT(array_el_ref, NotNull()); + EXPECT_THAT(array_el_ref->value.dataType, Eq(android::Res_value::TYPE_INT_BOOLEAN)); + EXPECT_THAT(array_el_ref->value.data, Eq(0xffffffffu)); + + auto actual_style = test::GetValue