diff options
Diffstat (limited to 'tools')
83 files changed, 3748 insertions, 1784 deletions
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h index d92de062bcf0..bf56ec024699 100644 --- a/tools/aapt/SdkConstants.h +++ b/tools/aapt/SdkConstants.h @@ -41,7 +41,8 @@ enum { SDK_MNC = 23, SDK_NOUGAT = 24, SDK_NOUGAT_MR1 = 25, - SDK_O = 26, // STOPSHIP replace with real version + SDK_O = 26, + SDK_O_MR1 = 27, }; #endif // H_AAPT_SDK_CONSTANTS diff --git a/tools/aapt/pseudolocalize.cpp b/tools/aapt/pseudolocalize.cpp index c7fee2c19342..5c47e0fa8a16 100644 --- a/tools/aapt/pseudolocalize.cpp +++ b/tools/aapt/pseudolocalize.cpp @@ -360,9 +360,15 @@ String16 PseudoMethodBidi::text(const String16& source) String16 result; bool lastspace = true; bool space = true; + bool escape = false; + const char16_t ESCAPE_CHAR = '\\'; for (size_t i=0; i<source.size(); i++) { char16_t c = s[i]; - space = is_space(c); + if (!escape && c == ESCAPE_CHAR) { + escape = true; + continue; + } + space = (!escape && is_space(c)) || (escape && (c == 'n' || c == 't')); if (lastspace && !space) { // Word start result += k_rlm + k_rlo; @@ -371,6 +377,10 @@ String16 PseudoMethodBidi::text(const String16& source) result += k_pdf + k_rlm; } lastspace = space; + if (escape) { + result.append(&ESCAPE_CHAR, 1); + escape=false; + } result.append(&c, 1); } if (!lastspace) { diff --git a/tools/aapt/tests/Pseudolocales_test.cpp b/tools/aapt/tests/Pseudolocales_test.cpp index 4670e9fd53ea..a6aed3abd7cf 100644 --- a/tools/aapt/tests/Pseudolocales_test.cpp +++ b/tools/aapt/tests/Pseudolocales_test.cpp @@ -87,6 +87,10 @@ TEST(Pseudolocales, PlaintextBidi) { "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \ " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", PSEUDO_BIDI); + simple_helper("hello\\nworld\\n", + "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\\n" + "\xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\\n", + PSEUDO_BIDI); } TEST(Pseudolocales, SimpleICU) { diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 1dc47f88ab92..14d05fdf6ee8 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -85,6 +85,7 @@ cc_library_host_static { "compile/Pseudolocalizer.cpp", "compile/XmlIdCollector.cpp", "configuration/ConfigurationParser.cpp", + "filter/AbiFilter.cpp", "filter/ConfigFilter.cpp", "flatten/Archive.cpp", "flatten/TableFlattener.cpp", @@ -110,6 +111,8 @@ cc_library_host_static { "proto/TableProtoDeserializer.cpp", "proto/TableProtoSerializer.cpp", "split/TableSplitter.cpp", + "text/Unicode.cpp", + "text/Utf8Iterator.cpp", "unflatten/BinaryResourceParser.cpp", "unflatten/ResChunkPullParser.cpp", "util/BigBuffer.cpp", diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index 8a8f8be205e7..7e5efa15f61b 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -57,6 +57,12 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context, bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, IArchiveWriter* writer) { + FilterChain empty; + return WriteToArchive(context, options, &empty, writer); +} + +bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, + FilterChain* filters, IArchiveWriter* writer) { std::set<std::string> referenced_resources; // List the files being referenced in the resource table. for (auto& pkg : table_->packages) { @@ -89,6 +95,13 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption continue; } + if (!filters->Keep(path)) { + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(DiagMessage() << "Filtered '" << path << "' from APK."); + } + continue; + } + // The resource table needs to be re-serialized since it might have changed. if (path == "resources.arsc") { BigBuffer buffer(4096); diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h index 59eb8161a868..8aa9674aa2ed 100644 --- a/tools/aapt2/LoadedApk.h +++ b/tools/aapt2/LoadedApk.h @@ -20,6 +20,7 @@ #include "androidfw/StringPiece.h" #include "ResourceTable.h" +#include "filter/Filter.h" #include "flatten/Archive.h" #include "flatten/TableFlattener.h" #include "io/ZipArchive.h" @@ -49,6 +50,14 @@ class LoadedApk { bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, IArchiveWriter* writer); + /** + * Writes the APK on disk at the given path, while also removing the resource + * files that are not referenced in the resource table. The provided filter + * chain is applied to each entry in the APK file. + */ + bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, + FilterChain* filters, IArchiveWriter* writer); + static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context, const android::StringPiece& path); diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 1d2e3a4f2df0..c5d38abcdf71 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -27,7 +27,7 @@ namespace aapt { static const char* sMajorVersion = "2"; // Update minor version whenever a feature or flag is added. -static const char* sMinorVersion = "16"; +static const char* sMinorVersion = "18"; int PrintVersion() { std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "." diff --git a/tools/aapt2/OWNERS b/tools/aapt2/OWNERS new file mode 100644 index 000000000000..d76233ec78ba --- /dev/null +++ b/tools/aapt2/OWNERS @@ -0,0 +1,2 @@ +set noparent +adamlesinski@google.com diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 9a37913f0edc..a5783a532e23 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -268,8 +268,7 @@ bool ResourceParser::Parse(xml::XmlPullParser* parser) { continue; } - if (!parser->element_namespace().empty() || - parser->element_name() != "resources") { + if (!parser->element_namespace().empty() || parser->element_name() != "resources") { diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) << "root element must be <resources>"); return false; @@ -328,8 +327,7 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { parsed_resource.comment = std::move(comment); // Extract the product name if it exists. - if (Maybe<StringPiece> maybe_product = - xml::FindNonEmptyAttribute(parser, "product")) { + if (Maybe<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) { parsed_resource.product = maybe_product.value().to_string(); } @@ -348,10 +346,8 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { for (const ResourceName& stripped_resource : stripped_resources) { if (!table_->FindResource(stripped_resource)) { // Failed to find the resource. - diag_->Error(DiagMessage(source_) - << "resource '" << stripped_resource - << "' " - "was filtered out but no product variant remains"); + diag_->Error(DiagMessage(source_) << "resource '" << stripped_resource + << "' was filtered out but no product variant remains"); error = true; } } @@ -589,7 +585,7 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, // This can only be a StyledString. std::unique_ptr<StyledString> styled_string = util::make_unique<StyledString>(table_->string_pool.MakeRef( - style_string, StringPool::Context(StringPool::Context::kStylePriority, config_))); + style_string, StringPool::Context(StringPool::Context::kNormalPriority, config_))); styled_string->untranslatable_sections = std::move(untranslatable_sections); return std::move(styled_string); } diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index c6382b177f42..1683c64a6a5c 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -25,11 +25,17 @@ #include "test/Test.h" #include "xml/XmlPullParser.h" +using ::aapt::test::StrValueEq; using ::aapt::test::ValueEq; +using ::android::ResTable_map; +using ::android::Res_value; using ::android::StringPiece; using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::IsNull; using ::testing::NotNull; using ::testing::Pointee; +using ::testing::SizeIs; namespace aapt { @@ -74,31 +80,31 @@ class ResourceParserTest : public ::testing::Test { }; TEST_F(ResourceParserTest, ParseQuotedString) { - std::string input = "<string name=\"foo\"> \" hey there \" </string>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<string name="foo"> " hey there " </string>)")); String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string(" hey there "), *str->value); - EXPECT_TRUE(str->untranslatable_sections.empty()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq(" hey there ")); + EXPECT_THAT(str->untranslatable_sections, IsEmpty()); } TEST_F(ResourceParserTest, ParseEscapedString) { - std::string input = "<string name=\"foo\">\\?123</string>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<string name="foo">\?123</string>)")); String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string("?123"), *str->value); - EXPECT_TRUE(str->untranslatable_sections.empty()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq("?123")); + EXPECT_THAT(str->untranslatable_sections, IsEmpty()); + + ASSERT_TRUE(TestParse(R"(<string name="bar">This isn\’t a bad string</string>)")); + str = test::GetValue<String>(&table_, "string/bar"); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq("This isn’t a bad string")); } TEST_F(ResourceParserTest, ParseFormattedString) { - std::string input = "<string name=\"foo\">%d %s</string>"; - ASSERT_FALSE(TestParse(input)); - - input = "<string name=\"foo\">%1$d %2$s</string>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_FALSE(TestParse(R"(<string name="foo">%d %s</string>)")); + ASSERT_TRUE(TestParse(R"(<string name="foo">%1$d %2$s</string>)")); } TEST_F(ResourceParserTest, ParseStyledString) { @@ -109,98 +115,93 @@ TEST_F(ResourceParserTest, ParseStyledString) { ASSERT_TRUE(TestParse(input)); StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); + ASSERT_THAT(str, NotNull()); - const std::string expected_str = "This is my aunt\u2019s fickle string"; - EXPECT_EQ(expected_str, *str->value->str); - EXPECT_EQ(2u, str->value->spans.size()); - EXPECT_TRUE(str->untranslatable_sections.empty()); + EXPECT_THAT(str->value->value, Eq("This is my aunt\u2019s fickle string")); + EXPECT_THAT(str->value->spans, SizeIs(2)); + EXPECT_THAT(str->untranslatable_sections, IsEmpty()); - EXPECT_EQ(std::string("b"), *str->value->spans[0].name); - EXPECT_EQ(17u, str->value->spans[0].first_char); - EXPECT_EQ(30u, str->value->spans[0].last_char); + EXPECT_THAT(*str->value->spans[0].name, Eq("b")); + EXPECT_THAT(str->value->spans[0].first_char, Eq(17u)); + EXPECT_THAT(str->value->spans[0].last_char, Eq(30u)); - EXPECT_EQ(std::string("small"), *str->value->spans[1].name); - EXPECT_EQ(24u, str->value->spans[1].first_char); - EXPECT_EQ(30u, str->value->spans[1].last_char); + EXPECT_THAT(*str->value->spans[1].name, Eq("small")); + EXPECT_THAT(str->value->spans[1].first_char, Eq(24u)); + EXPECT_THAT(str->value->spans[1].last_char, Eq(30u)); } TEST_F(ResourceParserTest, ParseStringWithWhitespace) { - std::string input = "<string name=\"foo\"> This is what I think </string>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<string name="foo"> This is what I think </string>)")); String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string("This is what I think"), *str->value); - EXPECT_TRUE(str->untranslatable_sections.empty()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str->value, Eq("This is what I think")); + EXPECT_THAT(str->untranslatable_sections, IsEmpty()); - input = "<string name=\"foo2\">\" This is what I think \"</string>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<string name="foo2">" This is what I think "</string>)")); str = test::GetValue<String>(&table_, "string/foo2"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string(" This is what I think "), *str->value); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq(" This is what I think ")); } TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) { - std::string input = R"EOF( + std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - There are <xliff:source>no</xliff:source> apples</string>)EOF"; + There are <xliff:source>no</xliff:source> apples</string>)"; ASSERT_TRUE(TestParse(input)); String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(StringPiece("There are no apples"), StringPiece(*str->value)); - EXPECT_TRUE(str->untranslatable_sections.empty()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq("There are no apples")); + EXPECT_THAT(str->untranslatable_sections, IsEmpty()); } TEST_F(ResourceParserTest, NestedXliffGTagsAreIllegal) { - std::string input = R"EOF( + std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - Do not <xliff:g>translate <xliff:g>this</xliff:g></xliff:g></string>)EOF"; + Do not <xliff:g>translate <xliff:g>this</xliff:g></xliff:g></string>)"; EXPECT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInString) { - std::string input = R"EOF( + std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - There are <xliff:g id="count">%1$d</xliff:g> apples</string>)EOF"; + There are <xliff:g id="count">%1$d</xliff:g> apples</string>)"; ASSERT_TRUE(TestParse(input)); String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value)); - - ASSERT_EQ(1u, str->untranslatable_sections.size()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq("There are %1$d apples")); + ASSERT_THAT(str->untranslatable_sections, SizeIs(1)); // We expect indices and lengths that span to include the whitespace // before %1$d. This is due to how the StringBuilder withholds whitespace unless // needed (to deal with line breaks, etc.). - EXPECT_EQ(9u, str->untranslatable_sections[0].start); - EXPECT_EQ(14u, str->untranslatable_sections[0].end); + EXPECT_THAT(str->untranslatable_sections[0].start, Eq(9u)); + EXPECT_THAT(str->untranslatable_sections[0].end, Eq(14u)); } TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInStyledString) { - std::string input = R"EOF( + std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - There are <b><xliff:g id="count">%1$d</xliff:g></b> apples</string>)EOF"; + There are <b><xliff:g id="count">%1$d</xliff:g></b> apples</string>)"; ASSERT_TRUE(TestParse(input)); StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value->str)); - - ASSERT_EQ(1u, str->untranslatable_sections.size()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(str->value->value, Eq("There are %1$d apples")); + ASSERT_THAT(str->untranslatable_sections, SizeIs(1)); // We expect indices and lengths that span to include the whitespace // before %1$d. This is due to how the StringBuilder withholds whitespace unless // needed (to deal with line breaks, etc.). - EXPECT_EQ(9u, str->untranslatable_sections[0].start); - EXPECT_EQ(14u, str->untranslatable_sections[0].end); + EXPECT_THAT(str->untranslatable_sections[0].start, Eq(9u)); + EXPECT_THAT(str->untranslatable_sections[0].end, Eq(14u)); } TEST_F(ResourceParserTest, ParseNull) { - std::string input = "<integer name=\"foo\">@null</integer>"; + std::string input = R"(<integer name="foo">@null</integer>)"; ASSERT_TRUE(TestParse(input)); // The Android runtime treats a value of android::Res_value::TYPE_NULL as @@ -211,38 +212,36 @@ TEST_F(ResourceParserTest, ParseNull) { ASSERT_THAT(null_ref, NotNull()); EXPECT_FALSE(null_ref->name); EXPECT_FALSE(null_ref->id); - EXPECT_EQ(Reference::Type::kResource, null_ref->reference_type); + EXPECT_THAT(null_ref->reference_type, Eq(Reference::Type::kResource)); } TEST_F(ResourceParserTest, ParseEmpty) { - std::string input = "<integer name=\"foo\">@empty</integer>"; + std::string input = R"(<integer name="foo">@empty</integer>)"; ASSERT_TRUE(TestParse(input)); BinaryPrimitive* integer = test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); - ASSERT_NE(nullptr, integer); - EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); - EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); + ASSERT_THAT(integer, NotNull()); + EXPECT_THAT(integer->value.dataType, Eq(Res_value::TYPE_NULL)); + EXPECT_THAT(integer->value.data, Eq(Res_value::DATA_NULL_EMPTY)); } TEST_F(ResourceParserTest, ParseAttr) { - std::string input = - "<attr name=\"foo\" format=\"string\"/>\n" - "<attr name=\"bar\"/>"; + std::string input = R"( + <attr name="foo" format="string"/> + <attr name="bar"/>)"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_STRING)); attr = test::GetValue<Attribute>(&table_, "attr/bar"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->type_mask); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_ANY)); } -// Old AAPT allowed attributes to be defined under different configurations, but -// ultimately -// stored them with the default configuration. Check that we have the same -// behavior. +// Old AAPT allowed attributes to be defined under different configurations, but ultimately +// stored them with the default configuration. Check that we have the same behavior. TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) { const ConfigDescription watch_config = test::ParseConfigOrDie("watch"); std::string input = R"( @@ -252,583 +251,519 @@ TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoC </declare-styleable>)"; ASSERT_TRUE(TestParse(input, watch_config)); - EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/foo", watch_config)); - EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/baz", watch_config)); - EXPECT_EQ(nullptr, test::GetValueForConfig<Styleable>(&table_, "styleable/bar", watch_config)); + EXPECT_THAT(test::GetValueForConfig<Attribute>(&table_, "attr/foo", watch_config), IsNull()); + EXPECT_THAT(test::GetValueForConfig<Attribute>(&table_, "attr/baz", watch_config), IsNull()); + EXPECT_THAT(test::GetValueForConfig<Styleable>(&table_, "styleable/bar", watch_config), IsNull()); - EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/foo")); - EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/baz")); - EXPECT_NE(nullptr, test::GetValue<Styleable>(&table_, "styleable/bar")); + EXPECT_THAT(test::GetValue<Attribute>(&table_, "attr/foo"), NotNull()); + EXPECT_THAT(test::GetValue<Attribute>(&table_, "attr/baz"), NotNull()); + EXPECT_THAT(test::GetValue<Styleable>(&table_, "styleable/bar"), NotNull()); } TEST_F(ResourceParserTest, ParseAttrWithMinMax) { - std::string input = - "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>"; + std::string input = R"(<attr name="foo" min="10" max="23" format="integer"/>)"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->type_mask); - EXPECT_EQ(10, attr->min_int); - EXPECT_EQ(23, attr->max_int); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_INTEGER)); + EXPECT_THAT(attr->min_int, Eq(10)); + EXPECT_THAT(attr->max_int, Eq(23)); } TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) { - std::string input = - "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>"; - ASSERT_FALSE(TestParse(input)); + ASSERT_FALSE(TestParse(R"(<attr name="foo" min="10" max="23" format="string"/>)")); } TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { - std::string input = - "<declare-styleable name=\"Styleable\">\n" - " <attr name=\"foo\" />\n" - "</declare-styleable>\n" - "<attr name=\"foo\" format=\"string\"/>"; + std::string input = R"( + <declare-styleable name="Styleable"> + <attr name="foo" /> + </declare-styleable> + <attr name="foo" format="string"/>)"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_STRING)); } TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { - std::string input = - "<declare-styleable name=\"Theme\">" - " <attr name=\"foo\" />\n" - "</declare-styleable>\n" - "<declare-styleable name=\"Window\">\n" - " <attr name=\"foo\" format=\"boolean\"/>\n" - "</declare-styleable>"; + std::string input = R"( + <declare-styleable name="Theme"> + <attr name="foo" /> + </declare-styleable> + <declare-styleable name="Window"> + <attr name="foo" format="boolean"/> + </declare-styleable>)"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->type_mask); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_BOOLEAN)); } TEST_F(ResourceParserTest, ParseEnumAttr) { - std::string input = - "<attr name=\"foo\">\n" - " <enum name=\"bar\" value=\"0\"/>\n" - " <enum name=\"bat\" value=\"1\"/>\n" - " <enum name=\"baz\" value=\"2\"/>\n" - "</attr>"; + std::string input = R"( + <attr name="foo"> + <enum name="bar" value="0"/> + <enum name="bat" value="1"/> + <enum name="baz" value="2"/> + </attr>)"; ASSERT_TRUE(TestParse(input)); Attribute* enum_attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(enum_attr, nullptr); - EXPECT_EQ(enum_attr->type_mask, android::ResTable_map::TYPE_ENUM); - ASSERT_EQ(enum_attr->symbols.size(), 3u); + ASSERT_THAT(enum_attr, NotNull()); + EXPECT_THAT(enum_attr->type_mask, Eq(ResTable_map::TYPE_ENUM)); + ASSERT_THAT(enum_attr->symbols, SizeIs(3)); - AAPT_ASSERT_TRUE(enum_attr->symbols[0].symbol.name); - EXPECT_EQ(enum_attr->symbols[0].symbol.name.value().entry, "bar"); - EXPECT_EQ(enum_attr->symbols[0].value, 0u); + ASSERT_TRUE(enum_attr->symbols[0].symbol.name); + EXPECT_THAT(enum_attr->symbols[0].symbol.name.value().entry, Eq("bar")); + EXPECT_THAT(enum_attr->symbols[0].value, Eq(0u)); - AAPT_ASSERT_TRUE(enum_attr->symbols[1].symbol.name); - EXPECT_EQ(enum_attr->symbols[1].symbol.name.value().entry, "bat"); - EXPECT_EQ(enum_attr->symbols[1].value, 1u); + ASSERT_TRUE(enum_attr->symbols[1].symbol.name); + EXPECT_THAT(enum_attr->symbols[1].symbol.name.value().entry, Eq("bat")); + EXPECT_THAT(enum_attr->symbols[1].value, Eq(1u)); - AAPT_ASSERT_TRUE(enum_attr->symbols[2].symbol.name); - EXPECT_EQ(enum_attr->symbols[2].symbol.name.value().entry, "baz"); - EXPECT_EQ(enum_attr->symbols[2].value, 2u); + ASSERT_TRUE(enum_attr->symbols[2].symbol.name); + EXPECT_THAT(enum_attr->symbols[2].symbol.name.value().entry, Eq("baz")); + EXPECT_THAT(enum_attr->symbols[2].value, Eq(2u)); } TEST_F(ResourceParserTest, ParseFlagAttr) { - std::string input = - "<attr name=\"foo\">\n" - " <flag name=\"bar\" value=\"0\"/>\n" - " <flag name=\"bat\" value=\"1\"/>\n" - " <flag name=\"baz\" value=\"2\"/>\n" - "</attr>"; + std::string input = R"( + <attr name="foo"> + <flag name="bar" value="0"/> + <flag name="bat" value="1"/> + <flag name="baz" value="2"/> + </attr>)"; ASSERT_TRUE(TestParse(input)); Attribute* flag_attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, flag_attr); - EXPECT_EQ(flag_attr->type_mask, android::ResTable_map::TYPE_FLAGS); - ASSERT_EQ(flag_attr->symbols.size(), 3u); + ASSERT_THAT(flag_attr, NotNull()); + EXPECT_THAT(flag_attr->type_mask, Eq(ResTable_map::TYPE_FLAGS)); + ASSERT_THAT(flag_attr->symbols, SizeIs(3)); - AAPT_ASSERT_TRUE(flag_attr->symbols[0].symbol.name); - EXPECT_EQ(flag_attr->symbols[0].symbol.name.value().entry, "bar"); - EXPECT_EQ(flag_attr->symbols[0].value, 0u); + ASSERT_TRUE(flag_attr->symbols[0].symbol.name); + EXPECT_THAT(flag_attr->symbols[0].symbol.name.value().entry, Eq("bar")); + EXPECT_THAT(flag_attr->symbols[0].value, Eq(0u)); - AAPT_ASSERT_TRUE(flag_attr->symbols[1].symbol.name); - EXPECT_EQ(flag_attr->symbols[1].symbol.name.value().entry, "bat"); - EXPECT_EQ(flag_attr->symbols[1].value, 1u); + ASSERT_TRUE(flag_attr->symbols[1].symbol.name); + EXPECT_THAT(flag_attr->symbols[1].symbol.name.value().entry, Eq("bat")); + EXPECT_THAT(flag_attr->symbols[1].value, Eq(1u)); - AAPT_ASSERT_TRUE(flag_attr->symbols[2].symbol.name); - EXPECT_EQ(flag_attr->symbols[2].symbol.name.value().entry, "baz"); - EXPECT_EQ(flag_attr->symbols[2].value, 2u); + ASSERT_TRUE(flag_attr->symbols[2].symbol.name); + EXPECT_THAT(flag_attr->symbols[2].symbol.name.value().entry, Eq("baz")); + EXPECT_THAT(flag_attr->symbols[2].value, Eq(2u)); std::unique_ptr<BinaryPrimitive> flag_value = ResourceUtils::TryParseFlagSymbol(flag_attr, "baz|bat"); - ASSERT_NE(nullptr, flag_value); - EXPECT_EQ(flag_value->value.data, 1u | 2u); + ASSERT_THAT(flag_value, NotNull()); + EXPECT_THAT(flag_value->value.data, Eq(1u | 2u)); } TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { - std::string input = - "<attr name=\"foo\">\n" - " <enum name=\"bar\" value=\"0\"/>\n" - " <enum name=\"bat\" value=\"1\"/>\n" - " <enum name=\"bat\" value=\"2\"/>\n" - "</attr>"; + std::string input = R"( + <attr name="foo"> + <enum name="bar" value="0"/> + <enum name="bat" value="1"/> + <enum name="bat" value="2"/> + </attr>)"; ASSERT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, ParseStyle) { - std::string input = - "<style name=\"foo\" parent=\"@style/fu\">\n" - " <item name=\"bar\">#ffffffff</item>\n" - " <item name=\"bat\">@string/hey</item>\n" - " <item name=\"baz\"><b>hey</b></item>\n" - "</style>"; + std::string input = R"( + <style name="foo" parent="@style/fu"> + <item name="bar">#ffffffff</item> + <item name="bat">@string/hey</item> + <item name="baz"><b>hey</b></item> + </style>)"; ASSERT_TRUE(TestParse(input)); Style* style = test::GetValue<Style>(&table_, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::ParseNameOrDie("style/fu"), - style->parent.value().name.value()); - ASSERT_EQ(3u, style->entries.size()); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); + EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("style/fu")))); + ASSERT_THAT(style->entries, SizeIs(3)); - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(test::ParseNameOrDie("attr/bar"), - style->entries[0].key.name.value()); - - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(test::ParseNameOrDie("attr/bat"), - style->entries[1].key.name.value()); - - AAPT_ASSERT_TRUE(style->entries[2].key.name); - EXPECT_EQ(test::ParseNameOrDie("attr/baz"), - style->entries[2].key.name.value()); + EXPECT_THAT(style->entries[0].key.name, Eq(make_value(test::ParseNameOrDie("attr/bar")))); + EXPECT_THAT(style->entries[1].key.name, Eq(make_value(test::ParseNameOrDie("attr/bat")))); + EXPECT_THAT(style->entries[2].key.name, Eq(make_value(test::ParseNameOrDie("attr/baz")))); } TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { - std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<style name="foo" parent="com.app:Theme"/>)")); Style* style = test::GetValue<Style>(&table_, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::ParseNameOrDie("com.app:style/Theme"), - style->parent.value().name.value()); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); + EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("com.app:style/Theme")))); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { - std::string input = - "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" - " name=\"foo\" parent=\"app:Theme\"/>"; + std::string input = R"( + <style xmlns:app="http://schemas.android.com/apk/res/android" + name="foo" parent="app:Theme"/>)"; ASSERT_TRUE(TestParse(input)); Style* style = test::GetValue<Style>(&table_, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::ParseNameOrDie("android:style/Theme"), - style->parent.value().name.value()); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); + ASSERT_TRUE(style->parent.value().name); + EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("android:style/Theme")))); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { - std::string input = - "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" " - "name=\"foo\">\n" - " <item name=\"app:bar\">0</item>\n" - "</style>"; + std::string input = R"( + <style xmlns:app="http://schemas.android.com/apk/res/android" name="foo"> + <item name="app:bar">0</item> + </style>)"; ASSERT_TRUE(TestParse(input)); Style* style = test::GetValue<Style>(&table_, "style/foo"); - ASSERT_NE(nullptr, style); - ASSERT_EQ(1u, style->entries.size()); - EXPECT_EQ(test::ParseNameOrDie("android:attr/bar"), - style->entries[0].key.name.value()); + ASSERT_THAT(style, NotNull()); + ASSERT_THAT(style->entries, SizeIs(1)); + EXPECT_THAT(style->entries[0].key.name, Eq(make_value(test::ParseNameOrDie("android:attr/bar")))); } TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { - std::string input = "<style name=\"foo.bar\"/>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<style name="foo.bar"/>)")); Style* style = test::GetValue<Style>(&table_, "style/foo.bar"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(style->parent.value().name.value(), - test::ParseNameOrDie("style/foo")); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); + EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("style/foo")))); EXPECT_TRUE(style->parent_inferred); } -TEST_F(ResourceParserTest, - ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { - std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; - ASSERT_TRUE(TestParse(input)); +TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { + ASSERT_TRUE(TestParse(R"(<style name="foo.bar" parent=""/>)")); Style* style = test::GetValue<Style>(&table_, "style/foo.bar"); - ASSERT_NE(nullptr, style); - AAPT_EXPECT_FALSE(style->parent); + ASSERT_THAT(style, NotNull()); + EXPECT_FALSE(style->parent); EXPECT_FALSE(style->parent_inferred); } TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) { - std::string input = - R"EOF(<style name="foo" parent="*android:style/bar" />)EOF"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<style name="foo" parent="*android:style/bar" />)")); Style* style = test::GetValue<Style>(&table_, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); EXPECT_TRUE(style->parent.value().private_reference); } TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { - std::string input = "<string name=\"foo\">@+id/bar</string>"; - ASSERT_TRUE(TestParse(input)); - - Id* id = test::GetValue<Id>(&table_, "id/bar"); - ASSERT_NE(id, nullptr); + ASSERT_TRUE(TestParse(R"(<string name="foo">@+id/bar</string>)")); + ASSERT_THAT(test::GetValue<Id>(&table_, "id/bar"), NotNull()); } TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { - std::string input = - "<declare-styleable name=\"foo\">\n" - " <attr name=\"bar\" />\n" - " <attr name=\"bat\" format=\"string|reference\"/>\n" - " <attr name=\"baz\">\n" - " <enum name=\"foo\" value=\"1\"/>\n" - " </attr>\n" - "</declare-styleable>"; + std::string input = R"( + <declare-styleable name="foo"> + <attr name="bar" /> + <attr name="bat" format="string|reference"/> + <attr name="baz"> + <enum name="foo" value="1"/> + </attr> + </declare-styleable>)"; ASSERT_TRUE(TestParse(input)); Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("styleable/foo")); - AAPT_ASSERT_TRUE(result); - EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state); + ASSERT_TRUE(result); + EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic)); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar"); - ASSERT_NE(attr, nullptr); + ASSERT_THAT(attr, NotNull()); EXPECT_TRUE(attr->IsWeak()); attr = test::GetValue<Attribute>(&table_, "attr/bat"); - ASSERT_NE(attr, nullptr); + ASSERT_THAT(attr, NotNull()); EXPECT_TRUE(attr->IsWeak()); attr = test::GetValue<Attribute>(&table_, "attr/baz"); - ASSERT_NE(attr, nullptr); + ASSERT_THAT(attr, NotNull()); EXPECT_TRUE(attr->IsWeak()); - EXPECT_EQ(1u, attr->symbols.size()); + EXPECT_THAT(attr->symbols, SizeIs(1)); - EXPECT_NE(nullptr, test::GetValue<Id>(&table_, "id/foo")); + EXPECT_THAT(test::GetValue<Id>(&table_, "id/foo"), NotNull()); Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); - ASSERT_NE(styleable, nullptr); - ASSERT_EQ(3u, styleable->entries.size()); + ASSERT_THAT(styleable, NotNull()); + ASSERT_THAT(styleable->entries, SizeIs(3)); - EXPECT_EQ(test::ParseNameOrDie("attr/bar"), - styleable->entries[0].name.value()); - EXPECT_EQ(test::ParseNameOrDie("attr/bat"), - styleable->entries[1].name.value()); + EXPECT_THAT(styleable->entries[0].name, Eq(make_value(test::ParseNameOrDie("attr/bar")))); + EXPECT_THAT(styleable->entries[1].name, Eq(make_value(test::ParseNameOrDie("attr/bat")))); + EXPECT_THAT(styleable->entries[2].name, Eq(make_value(test::ParseNameOrDie("attr/baz")))); } TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) { - std::string input = - "<declare-styleable name=\"foo\" " - "xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n" - " <attr name=\"*android:bar\" />\n" - " <attr name=\"privAndroid:bat\" />\n" - "</declare-styleable>"; + std::string input = R"( + <declare-styleable xmlns:privAndroid="http://schemas.android.com/apk/prv/res/android" + name="foo"> + <attr name="*android:bar" /> + <attr name="privAndroid:bat" /> + </declare-styleable>)"; ASSERT_TRUE(TestParse(input)); Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); - ASSERT_NE(nullptr, styleable); - ASSERT_EQ(2u, styleable->entries.size()); + ASSERT_THAT(styleable, NotNull()); + ASSERT_THAT(styleable->entries, SizeIs(2)); EXPECT_TRUE(styleable->entries[0].private_reference); - AAPT_ASSERT_TRUE(styleable->entries[0].name); - EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package); + ASSERT_TRUE(styleable->entries[0].name); + EXPECT_THAT(styleable->entries[0].name.value().package, Eq("android")); EXPECT_TRUE(styleable->entries[1].private_reference); - AAPT_ASSERT_TRUE(styleable->entries[1].name); - EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package); + ASSERT_TRUE(styleable->entries[1].name); + EXPECT_THAT(styleable->entries[1].name.value().package, Eq("android")); } TEST_F(ResourceParserTest, ParseArray) { - std::string input = - "<array name=\"foo\">\n" - " <item>@string/ref</item>\n" - " <item>hey</item>\n" - " <item>23</item>\n" - "</array>"; + std::string input = R"( + <array name="foo"> + <item>@string/ref</item> + <item>hey</item> + <item>23</item> + </array>)"; ASSERT_TRUE(TestParse(input)); Array* array = test::GetValue<Array>(&table_, "array/foo"); - ASSERT_NE(array, nullptr); - ASSERT_EQ(3u, array->items.size()); + ASSERT_THAT(array, NotNull()); + ASSERT_THAT(array->items, SizeIs(3)); - EXPECT_NE(nullptr, ValueCast<Reference>(array->items[0].get())); - EXPECT_NE(nullptr, ValueCast<String>(array->items[1].get())); - EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(array->items[2].get())); + EXPECT_THAT(ValueCast<Reference>(array->items[0].get()), NotNull()); + EXPECT_THAT(ValueCast<String>(array->items[1].get()), NotNull()); + EXPECT_THAT(ValueCast<BinaryPrimitive>(array->items[2].get()), NotNull()); } TEST_F(ResourceParserTest, ParseStringArray) { - std::string input = R"EOF( + std::string input = R"( <string-array name="foo"> <item>"Werk"</item>" - </string-array>)EOF"; + </string-array>)"; ASSERT_TRUE(TestParse(input)); - EXPECT_NE(nullptr, test::GetValue<Array>(&table_, "array/foo")); + EXPECT_THAT(test::GetValue<Array>(&table_, "array/foo"), NotNull()); } TEST_F(ResourceParserTest, ParseArrayWithFormat) { - std::string input = R"EOF( + std::string input = R"( <array name="foo" format="string"> <item>100</item> - </array>)EOF"; + </array>)"; ASSERT_TRUE(TestParse(input)); Array* array = test::GetValue<Array>(&table_, "array/foo"); - ASSERT_NE(nullptr, array); - - ASSERT_EQ(1u, array->items.size()); + ASSERT_THAT(array, NotNull()); + ASSERT_THAT(array->items, SizeIs(1)); String* str = ValueCast<String>(array->items[0].get()); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string("100"), *str->value); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq("100")); } TEST_F(ResourceParserTest, ParseArrayWithBadFormat) { - std::string input = R"EOF( + std::string input = R"( <array name="foo" format="integer"> <item>Hi</item> - </array>)EOF"; + </array>)"; ASSERT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, ParsePlural) { - std::string input = - "<plurals name=\"foo\">\n" - " <item quantity=\"other\">apples</item>\n" - " <item quantity=\"one\">apple</item>\n" - "</plurals>"; + std::string input = R"( + <plurals name="foo"> + <item quantity="other">apples</item> + <item quantity="one">apple</item> + </plurals>)"; ASSERT_TRUE(TestParse(input)); Plural* plural = test::GetValue<Plural>(&table_, "plurals/foo"); - ASSERT_NE(nullptr, plural); - EXPECT_EQ(nullptr, plural->values[Plural::Zero]); - EXPECT_EQ(nullptr, plural->values[Plural::Two]); - EXPECT_EQ(nullptr, plural->values[Plural::Few]); - EXPECT_EQ(nullptr, plural->values[Plural::Many]); + ASSERT_THAT(plural, NotNull()); + EXPECT_THAT(plural->values[Plural::Zero], IsNull()); + EXPECT_THAT(plural->values[Plural::Two], IsNull()); + EXPECT_THAT(plural->values[Plural::Few], IsNull()); + EXPECT_THAT(plural->values[Plural::Many], IsNull()); - EXPECT_NE(nullptr, plural->values[Plural::One]); - EXPECT_NE(nullptr, plural->values[Plural::Other]); + EXPECT_THAT(plural->values[Plural::One], NotNull()); + EXPECT_THAT(plural->values[Plural::Other], NotNull()); } TEST_F(ResourceParserTest, ParseCommentsWithResource) { - std::string input = - "<!--This is a comment-->\n" - "<string name=\"foo\">Hi</string>"; + std::string input = R"( + <!--This is a comment--> + <string name="foo">Hi</string>)"; ASSERT_TRUE(TestParse(input)); String* value = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->GetComment(), "This is a comment"); + ASSERT_THAT(value, NotNull()); + EXPECT_THAT(value->GetComment(), Eq("This is a comment")); } TEST_F(ResourceParserTest, DoNotCombineMultipleComments) { - std::string input = - "<!--One-->\n" - "<!--Two-->\n" - "<string name=\"foo\">Hi</string>"; + std::string input = R"( + <!--One--> + <!--Two--> + <string name="foo">Hi</string>)"; ASSERT_TRUE(TestParse(input)); String* value = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->GetComment(), "Two"); + ASSERT_THAT(value, NotNull()); + EXPECT_THAT(value->GetComment(), Eq("Two")); } TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) { - std::string input = - "<!--One-->\n" - "<string name=\"foo\">\n" - " Hi\n" - "<!--Two-->\n" - "</string>"; - + std::string input = R"( + <!--One--> + <string name="foo"> + Hi + <!--Two--> + </string>)"; ASSERT_TRUE(TestParse(input)); String* value = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->GetComment(), "One"); + ASSERT_THAT(value, NotNull()); + EXPECT_THAT(value->GetComment(), Eq("One")); } TEST_F(ResourceParserTest, ParseNestedComments) { // We only care about declare-styleable and enum/flag attributes because - // comments - // from those end up in R.java - std::string input = R"EOF( - <declare-styleable name="foo"> - <!-- The name of the bar --> - <attr name="barName" format="string|reference" /> - </declare-styleable> - - <attr name="foo"> - <!-- The very first --> - <enum name="one" value="1" /> - </attr>)EOF"; + // comments from those end up in R.java + std::string input = R"( + <declare-styleable name="foo"> + <!-- The name of the bar --> + <attr name="barName" format="string|reference" /> + </declare-styleable> + + <attr name="foo"> + <!-- The very first --> + <enum name="one" value="1" /> + </attr>)"; ASSERT_TRUE(TestParse(input)); Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); - ASSERT_NE(nullptr, styleable); - ASSERT_EQ(1u, styleable->entries.size()); - - EXPECT_EQ(StringPiece("The name of the bar"), - styleable->entries.front().GetComment()); + ASSERT_THAT(styleable, NotNull()); + ASSERT_THAT(styleable->entries, SizeIs(1)); + EXPECT_THAT(styleable->entries[0].GetComment(), Eq("The name of the bar")); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, attr); - ASSERT_EQ(1u, attr->symbols.size()); - - EXPECT_EQ(StringPiece("The very first"), - attr->symbols.front().symbol.GetComment()); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->symbols, SizeIs(1)); + EXPECT_THAT(attr->symbols[0].symbol.GetComment(), Eq("The very first")); } -/* - * Declaring an ID as public should not require a separate definition - * (as an ID has no value). - */ +// Declaring an ID as public should not require a separate definition (as an ID has no value). TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { - std::string input = "<public type=\"id\" name=\"foo\"/>"; - ASSERT_TRUE(TestParse(input)); - - Id* id = test::GetValue<Id>(&table_, "id/foo"); - ASSERT_NE(nullptr, id); + ASSERT_TRUE(TestParse(R"(<public type="id" name="foo"/>)")); + ASSERT_THAT(test::GetValue<Id>(&table_, "id/foo"), NotNull()); } TEST_F(ResourceParserTest, KeepAllProducts) { - std::string input = R"EOF( - <string name="foo" product="phone">hi</string> - <string name="foo" product="no-sdcard">ho</string> - <string name="bar" product="">wee</string> - <string name="baz">woo</string> - <string name="bit" product="phablet">hoot</string> - <string name="bot" product="default">yes</string> - )EOF"; + std::string input = R"( + <string name="foo" product="phone">hi</string> + <string name="foo" product="no-sdcard">ho</string> + <string name="bar" product="">wee</string> + <string name="baz">woo</string> + <string name="bit" product="phablet">hoot</string> + <string name="bot" product="default">yes</string>)"; ASSERT_TRUE(TestParse(input)); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( - &table_, "string/foo", - ConfigDescription::DefaultConfig(), "phone")); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( - &table_, "string/foo", - ConfigDescription::DefaultConfig(), "no-sdcard")); - EXPECT_NE(nullptr, - test::GetValueForConfigAndProduct<String>( - &table_, "string/bar", ConfigDescription::DefaultConfig(), "")); - EXPECT_NE(nullptr, - test::GetValueForConfigAndProduct<String>( - &table_, "string/baz", ConfigDescription::DefaultConfig(), "")); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( - &table_, "string/bit", - ConfigDescription::DefaultConfig(), "phablet")); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( - &table_, "string/bot", - ConfigDescription::DefaultConfig(), "default")); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/foo", ConfigDescription::DefaultConfig(), "phone"), NotNull()); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/foo",ConfigDescription::DefaultConfig(), "no-sdcard"), NotNull()); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/bar", ConfigDescription::DefaultConfig(), ""), NotNull()); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/baz", ConfigDescription::DefaultConfig(), ""), NotNull()); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/bit", ConfigDescription::DefaultConfig(), "phablet"), NotNull()); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/bot", ConfigDescription::DefaultConfig(), "default"), NotNull()); } TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { - std::string input = R"EOF( - <public-group type="attr" first-id="0x01010040"> - <public name="foo" /> - <public name="bar" /> - </public-group>)EOF"; + std::string input = R"( + <public-group type="attr" first-id="0x01010040"> + <public name="foo" /> + <public name="bar" /> + </public-group>)"; ASSERT_TRUE(TestParse(input)); - Maybe<ResourceTable::SearchResult> result = - table_.FindResource(test::ParseNameOrDie("attr/foo")); - AAPT_ASSERT_TRUE(result); + Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("attr/foo")); + ASSERT_TRUE(result); - AAPT_ASSERT_TRUE(result.value().package->id); - AAPT_ASSERT_TRUE(result.value().type->id); - AAPT_ASSERT_TRUE(result.value().entry->id); + ASSERT_TRUE(result.value().package->id); + ASSERT_TRUE(result.value().type->id); + ASSERT_TRUE(result.value().entry->id); ResourceId actual_id(result.value().package->id.value(), result.value().type->id.value(), result.value().entry->id.value()); - EXPECT_EQ(ResourceId(0x01010040), actual_id); + EXPECT_THAT(actual_id, Eq(ResourceId(0x01010040))); result = table_.FindResource(test::ParseNameOrDie("attr/bar")); - AAPT_ASSERT_TRUE(result); + ASSERT_TRUE(result); - AAPT_ASSERT_TRUE(result.value().package->id); - AAPT_ASSERT_TRUE(result.value().type->id); - AAPT_ASSERT_TRUE(result.value().entry->id); + ASSERT_TRUE(result.value().package->id); + ASSERT_TRUE(result.value().type->id); + ASSERT_TRUE(result.value().entry->id); actual_id = ResourceId(result.value().package->id.value(), result.value().type->id.value(), result.value().entry->id.value()); - EXPECT_EQ(ResourceId(0x01010041), actual_id); + EXPECT_THAT(actual_id, Eq(ResourceId(0x01010041))); } TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) { - std::string input = - R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF"; - ASSERT_TRUE(TestParse(input)); - - input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF"; - ASSERT_FALSE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<item type="layout" name="foo">@layout/bar</item>)")); + ASSERT_FALSE(TestParse(R"(<item type="layout" name="bar">"this is a string"</item>)")); } TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) { - std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<add-resource name="bar" type="string" />)")); Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("string/bar")); - AAPT_ASSERT_TRUE(result); + ASSERT_TRUE(result); const ResourceEntry* entry = result.value().entry; - ASSERT_NE(nullptr, entry); - EXPECT_EQ(SymbolState::kUndefined, entry->symbol_status.state); + ASSERT_THAT(entry, NotNull()); + EXPECT_THAT(entry->symbol_status.state, Eq(SymbolState::kUndefined)); EXPECT_TRUE(entry->symbol_status.allow_new); } TEST_F(ResourceParserTest, ParseItemElementWithFormat) { - std::string input = R"(<item name="foo" type="integer" format="float">0.3</item>)"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<item name="foo" type="integer" format="float">0.3</item>)")); BinaryPrimitive* val = test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); ASSERT_THAT(val, NotNull()); - EXPECT_THAT(val->value.dataType, Eq(android::Res_value::TYPE_FLOAT)); + EXPECT_THAT(val->value.dataType, Eq(Res_value::TYPE_FLOAT)); - input = R"(<item name="bar" type="integer" format="fraction">100</item>)"; - ASSERT_FALSE(TestParse(input)); + ASSERT_FALSE(TestParse(R"(<item name="bar" type="integer" format="fraction">100</item>)")); } // An <item> without a format specifier accepts all types of values. TEST_F(ResourceParserTest, ParseItemElementWithoutFormat) { - std::string input = R"(<item name="foo" type="integer">100%p</item>)"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<item name="foo" type="integer">100%p</item>)")); BinaryPrimitive* val = test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); ASSERT_THAT(val, NotNull()); - EXPECT_THAT(val->value.dataType, Eq(android::Res_value::TYPE_FRACTION)); + EXPECT_THAT(val->value.dataType, Eq(Res_value::TYPE_FRACTION)); } TEST_F(ResourceParserTest, ParseConfigVaryingItem) { - std::string input = R"EOF(<item name="foo" type="configVarying">Hey</item>)EOF"; - ASSERT_TRUE(TestParse(input)); - ASSERT_NE(nullptr, test::GetValue<String>(&table_, "configVarying/foo")); + ASSERT_TRUE(TestParse(R"(<item name="foo" type="configVarying">Hey</item>)")); + ASSERT_THAT(test::GetValue<String>(&table_, "configVarying/foo"), NotNull()); } TEST_F(ResourceParserTest, ParseBagElement) { - std::string input = - R"EOF(<bag name="bag" type="configVarying"><item name="test">Hello!</item></bag>)EOF"; + std::string input = R"( + <bag name="bag" type="configVarying"> + <item name="test">Hello!</item> + </bag>)"; ASSERT_TRUE(TestParse(input)); Style* val = test::GetValue<Style>(&table_, "configVarying/bag"); - ASSERT_NE(nullptr, val); + ASSERT_THAT(val, NotNull()); + ASSERT_THAT(val->entries, SizeIs(1)); - ASSERT_EQ(1u, val->entries.size()); - EXPECT_EQ(Reference(test::ParseNameOrDie("attr/test")), val->entries[0].key); - EXPECT_NE(nullptr, ValueCast<RawString>(val->entries[0].value.get())); + EXPECT_THAT(val->entries[0].key, Eq(Reference(test::ParseNameOrDie("attr/test")))); + EXPECT_THAT(ValueCast<RawString>(val->entries[0].value.get()), NotNull()); } TEST_F(ResourceParserTest, ParseElementWithNoValue) { @@ -840,12 +775,11 @@ TEST_F(ResourceParserTest, ParseElementWithNoValue) { String* str = test::GetValue<String>(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); - EXPECT_THAT(*str->value, Eq("")); + EXPECT_THAT(*str, StrValueEq("")); } TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) { - std::string input = R"(<string name="foo">%1$s %n %2$s</string>)"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<string name="foo">%1$s %n %2$s</string>)")); } } // namespace aapt diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 168004f0b721..ab59560d33a3 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -15,20 +15,24 @@ */ #include "ResourceTable.h" -#include "ConfigDescription.h" -#include "NameMangler.h" -#include "ResourceValues.h" -#include "ValueVisitor.h" -#include "util/Util.h" -#include <android-base/logging.h> -#include <androidfw/ResourceTypes.h> #include <algorithm> #include <memory> #include <string> #include <tuple> -using android::StringPiece; +#include "android-base/logging.h" +#include "androidfw/ResourceTypes.h" + +#include "ConfigDescription.h" +#include "NameMangler.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" +#include "text/Unicode.h" +#include "util/Util.h" + +using ::aapt::text::IsValidResourceEntryName; +using ::android::StringPiece; namespace aapt { @@ -283,12 +287,9 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist return CollisionResult::kConflict; } -static constexpr const char* kValidNameChars = "._-"; - static StringPiece ValidateName(const StringPiece& name) { - auto iter = util::FindNonAlphaNumericAndNotInSet(name, kValidNameChars); - if (iter != name.end()) { - return StringPiece(iter, 1); + if (!IsValidResourceEntryName(name)) { + return name; } return {}; } diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index e2b37be994ff..2a3c131f7b4b 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -24,6 +24,8 @@ #include <ostream> #include <string> +using ::testing::NotNull; + namespace aapt { TEST(ResourceTableTest, FailToAddResourceWithBadName) { @@ -56,7 +58,7 @@ TEST(ResourceTableTest, AddOneResource) { test::ValueBuilder<Id>().SetSource("test/path/file.xml", 23u).Build(), test::GetDiagnostics())); - ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/id")); + EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/id"), NotNull()); } TEST(ResourceTableTest, AddMultipleResources) { @@ -88,11 +90,10 @@ TEST(ResourceTableTest, AddMultipleResources) { .Build(), test::GetDiagnostics())); - ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/layout_width")); - ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/id")); - ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:string/ok")); - ASSERT_NE(nullptr, test::GetValueForConfig<BinaryPrimitive>( - &table, "android:string/ok", language_config)); + EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/layout_width"), NotNull()); + EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/id"), NotNull()); + EXPECT_THAT(test::GetValue<Id>(&table, "android:string/ok"), NotNull()); + EXPECT_THAT(test::GetValueForConfig<BinaryPrimitive>(&table, "android:string/ok", language_config), NotNull()); } TEST(ResourceTableTest, OverrideWeakResourceValue) { @@ -103,7 +104,7 @@ TEST(ResourceTableTest, OverrideWeakResourceValue) { util::make_unique<Attribute>(true), test::GetDiagnostics())); Attribute* attr = test::GetValue<Attribute>(&table, "android:attr/foo"); - ASSERT_NE(nullptr, attr); + ASSERT_THAT(attr, NotNull()); EXPECT_TRUE(attr->IsWeak()); ASSERT_TRUE(table.AddResource( @@ -111,7 +112,7 @@ TEST(ResourceTableTest, OverrideWeakResourceValue) { util::make_unique<Attribute>(false), test::GetDiagnostics())); attr = test::GetValue<Attribute>(&table, "android:attr/foo"); - ASSERT_NE(nullptr, attr); + ASSERT_THAT(attr, NotNull()); EXPECT_FALSE(attr->IsWeak()); } @@ -127,16 +128,12 @@ TEST(ResourceTableTest, ProductVaryingValues) { util::make_unique<Id>(), test::GetDiagnostics())); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( - &table, "android:string/foo", - test::ParseConfigOrDie("land"), "tablet")); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( - &table, "android:string/foo", - test::ParseConfigOrDie("land"), "phone")); + EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "tablet"), NotNull()); + EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "phone"), NotNull()); Maybe<ResourceTable::SearchResult> sr = table.FindResource(test::ParseNameOrDie("android:string/foo")); - AAPT_ASSERT_TRUE(sr); + ASSERT_TRUE(sr); std::vector<ResourceConfigValue*> values = sr.value().entry->FindAllValues(test::ParseConfigOrDie("land")); ASSERT_EQ(2u, values.size()); diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index deeef6ebbcb7..f193fe0c6593 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -512,7 +512,7 @@ std::unique_ptr<BinaryPrimitive> MakeBool(bool val) { } std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) { - std::u16string str16 = util::Utf8ToUtf16(str); + std::u16string str16 = util::Utf8ToUtf16(util::TrimWhitespace(str)); android::Res_value value; if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { return {}; @@ -521,7 +521,7 @@ std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) { } std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) { - std::u16string str16 = util::Utf8ToUtf16(str); + std::u16string str16 = util::Utf8ToUtf16(util::TrimWhitespace(str)); android::Res_value value; if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) { return {}; @@ -700,7 +700,7 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config spans++; } return util::make_unique<StyledString>(dst_pool->MakeRef( - style_str, StringPool::Context(StringPool::Context::kStylePriority, config))); + style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); } else { if (type != ResourceType::kString && util::StartsWith(str, "res/")) { // This must be a FileReference. diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index cdc34f1ec752..e637c3ee2f3c 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -20,96 +20,90 @@ #include "test/Test.h" using ::aapt::test::ValueEq; +using ::android::Res_value; +using ::android::ResTable_map; +using ::testing::Eq; +using ::testing::NotNull; using ::testing::Pointee; namespace aapt { TEST(ResourceUtilsTest, ParseBool) { - EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("true")); - EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("TRUE")); - EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("True")); - EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("false")); - EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("FALSE")); - EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("False")); + EXPECT_THAT(ResourceUtils::ParseBool("true"), Eq(Maybe<bool>(true))); + EXPECT_THAT(ResourceUtils::ParseBool("TRUE"), Eq(Maybe<bool>(true))); + EXPECT_THAT(ResourceUtils::ParseBool("True"), Eq(Maybe<bool>(true))); + + EXPECT_THAT(ResourceUtils::ParseBool("false"), Eq(Maybe<bool>(false))); + EXPECT_THAT(ResourceUtils::ParseBool("FALSE"), Eq(Maybe<bool>(false))); + EXPECT_THAT(ResourceUtils::ParseBool("False"), Eq(Maybe<bool>(false))); + + EXPECT_THAT(ResourceUtils::ParseBool(" False\n "), Eq(Maybe<bool>(false))); } TEST(ResourceUtilsTest, ParseResourceName) { ResourceNameRef actual; bool actual_priv = false; - EXPECT_TRUE(ResourceUtils::ParseResourceName("android:color/foo", &actual, - &actual_priv)); - EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual); + EXPECT_TRUE(ResourceUtils::ParseResourceName("android:color/foo", &actual, &actual_priv)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kColor, "foo"))); EXPECT_FALSE(actual_priv); - EXPECT_TRUE( - ResourceUtils::ParseResourceName("color/foo", &actual, &actual_priv)); - EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual); + EXPECT_TRUE(ResourceUtils::ParseResourceName("color/foo", &actual, &actual_priv)); + EXPECT_THAT(actual, Eq(ResourceNameRef({}, ResourceType::kColor, "foo"))); EXPECT_FALSE(actual_priv); - EXPECT_TRUE(ResourceUtils::ParseResourceName("*android:color/foo", &actual, - &actual_priv)); - EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual); + EXPECT_TRUE(ResourceUtils::ParseResourceName("*android:color/foo", &actual, &actual_priv)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kColor, "foo"))); EXPECT_TRUE(actual_priv); EXPECT_FALSE(ResourceUtils::ParseResourceName(android::StringPiece(), &actual, &actual_priv)); } TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) { - ResourceNameRef expected({}, ResourceType::kColor, "foo"); ResourceNameRef actual; bool create = false; bool private_ref = false; - EXPECT_TRUE(ResourceUtils::ParseReference("@color/foo", &actual, &create, - &private_ref)); - EXPECT_EQ(expected, actual); + EXPECT_TRUE(ResourceUtils::ParseReference("@color/foo", &actual, &create, &private_ref)); + EXPECT_THAT(actual, Eq(ResourceNameRef({}, ResourceType::kColor, "foo"))); EXPECT_FALSE(create); EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParseReferenceWithPackage) { - ResourceNameRef expected("android", ResourceType::kColor, "foo"); ResourceNameRef actual; bool create = false; bool private_ref = false; - EXPECT_TRUE(ResourceUtils::ParseReference("@android:color/foo", &actual, - &create, &private_ref)); - EXPECT_EQ(expected, actual); + EXPECT_TRUE(ResourceUtils::ParseReference("@android:color/foo", &actual, &create, &private_ref)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kColor, "foo"))); EXPECT_FALSE(create); EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) { - ResourceNameRef expected("android", ResourceType::kColor, "foo"); ResourceNameRef actual; bool create = false; bool private_ref = false; - EXPECT_TRUE(ResourceUtils::ParseReference("\t @android:color/foo\n \n\t", - &actual, &create, &private_ref)); - EXPECT_EQ(expected, actual); + EXPECT_TRUE(ResourceUtils::ParseReference("\t @android:color/foo\n \n\t", &actual, &create, &private_ref)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kColor, "foo"))); EXPECT_FALSE(create); EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParseAutoCreateIdReference) { - ResourceNameRef expected("android", ResourceType::kId, "foo"); ResourceNameRef actual; bool create = false; bool private_ref = false; - EXPECT_TRUE(ResourceUtils::ParseReference("@+android:id/foo", &actual, - &create, &private_ref)); - EXPECT_EQ(expected, actual); + EXPECT_TRUE(ResourceUtils::ParseReference("@+android:id/foo", &actual, &create, &private_ref)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kId, "foo"))); EXPECT_TRUE(create); EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParsePrivateReference) { - ResourceNameRef expected("android", ResourceType::kId, "foo"); ResourceNameRef actual; bool create = false; bool private_ref = false; - EXPECT_TRUE(ResourceUtils::ParseReference("@*android:id/foo", &actual, - &create, &private_ref)); - EXPECT_EQ(expected, actual); + EXPECT_TRUE(ResourceUtils::ParseReference("@*android:id/foo", &actual, &create, &private_ref)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kId, "foo"))); EXPECT_FALSE(create); EXPECT_TRUE(private_ref); } @@ -118,8 +112,7 @@ TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { bool create = false; bool private_ref = false; ResourceNameRef actual; - EXPECT_FALSE(ResourceUtils::ParseReference("@+android:color/foo", &actual, - &create, &private_ref)); + EXPECT_FALSE(ResourceUtils::ParseReference("@+android:color/foo", &actual, &create, &private_ref)); } TEST(ResourceUtilsTest, ParseAttributeReferences) { @@ -143,82 +136,81 @@ TEST(ResourceUtilsTest, FailParseIncompleteReference) { } TEST(ResourceUtilsTest, ParseStyleParentReference) { - const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle, - "foo"); + const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle, "foo"); const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo"); std::string err_str; - Maybe<Reference> ref = - ResourceUtils::ParseStyleParentReference("@android:style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + Maybe<Reference> ref = ResourceUtils::ParseStyleParentReference("@android:style/foo", &err_str); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("@style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kStyleFooName))); - ref = - ResourceUtils::ParseStyleParentReference("?android:style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + ref = ResourceUtils::ParseStyleParentReference("?android:style/foo", &err_str); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("?style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("android:style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("android:foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("@android:foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kStyleFooName))); - ref = - ResourceUtils::ParseStyleParentReference("*android:style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + ref = ResourceUtils::ParseStyleParentReference("*android:style/foo", &err_str); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); EXPECT_TRUE(ref.value().private_reference); } TEST(ResourceUtilsTest, ParseEmptyFlag) { std::unique_ptr<Attribute> attr = test::AttributeBuilder(false) - .SetTypeMask(android::ResTable_map::TYPE_FLAGS) + .SetTypeMask(ResTable_map::TYPE_FLAGS) .AddItem("one", 0x01) .AddItem("two", 0x02) .Build(); - std::unique_ptr<BinaryPrimitive> result = - ResourceUtils::TryParseFlagSymbol(attr.get(), ""); - ASSERT_NE(nullptr, result); - EXPECT_EQ(0u, result->value.data); + std::unique_ptr<BinaryPrimitive> result = ResourceUtils::TryParseFlagSymbol(attr.get(), ""); + ASSERT_THAT(result, NotNull()); + EXPECT_THAT(result->value.data, Eq(0u)); } TEST(ResourceUtilsTest, NullIsEmptyReference) { - auto null_value = ResourceUtils::MakeNull(); - ASSERT_THAT(null_value, Pointee(ValueEq(Reference()))); - - auto value = ResourceUtils::TryParseNullOrEmpty("@null"); - ASSERT_THAT(value, Pointee(ValueEq(Reference()))); + ASSERT_THAT(ResourceUtils::MakeNull(), Pointee(ValueEq(Reference()))); + ASSERT_THAT(ResourceUtils::TryParseNullOrEmpty("@null"), Pointee(ValueEq(Reference()))); } TEST(ResourceUtilsTest, EmptyIsBinaryPrimitive) { - auto empty_value = ResourceUtils::MakeEmpty(); - ASSERT_THAT(empty_value, Pointee(ValueEq(BinaryPrimitive(android::Res_value::TYPE_NULL, - android::Res_value::DATA_NULL_EMPTY)))); + ASSERT_THAT(ResourceUtils::MakeEmpty(), Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_NULL, Res_value::DATA_NULL_EMPTY)))); + ASSERT_THAT(ResourceUtils::TryParseNullOrEmpty("@empty"), Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_NULL, Res_value::DATA_NULL_EMPTY)))); +} + +TEST(ResourceUtilsTest, ItemsWithWhitespaceAreParsedCorrectly) { + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" 12\n ", ResTable_map::TYPE_INTEGER), + Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_INT_DEC, 12u)))); + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" true\n ", ResTable_map::TYPE_BOOLEAN), + Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_INT_BOOLEAN, 0xffffffffu)))); - auto value = ResourceUtils::TryParseNullOrEmpty("@empty"); - ASSERT_THAT(value, Pointee(ValueEq(BinaryPrimitive(android::Res_value::TYPE_NULL, - android::Res_value::DATA_NULL_EMPTY)))); + const float expected_float = 12.0f; + const uint32_t expected_float_flattened = *(uint32_t*)&expected_float; + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" 12.0\n ", ResTable_map::TYPE_FLOAT), + Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened)))); } } // namespace aapt diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index e808984c75f5..eb59175edf3b 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -253,10 +253,9 @@ StyledString* StyledString::Clone(StringPool* new_pool) const { } void StyledString::Print(std::ostream* out) const { - *out << "(styled string) \"" << *value->str << "\""; + *out << "(styled string) \"" << value->value << "\""; for (const StringPool::Span& span : value->spans) { - *out << " " << *span.name << ":" << span.first_char << "," - << span.last_char; + *out << " " << *span.name << ":" << span.first_char << "," << span.last_char; } } @@ -533,75 +532,119 @@ void Attribute::Print(std::ostream* out) const { } } -static void BuildAttributeMismatchMessage(DiagMessage* msg, - const Attribute* attr, - const Item* value) { - *msg << "expected"; - if (attr->type_mask & android::ResTable_map::TYPE_BOOLEAN) { - *msg << " boolean"; +static void BuildAttributeMismatchMessage(const Attribute& attr, const Item& value, + DiagMessage* out_msg) { + *out_msg << "expected"; + if (attr.type_mask & android::ResTable_map::TYPE_BOOLEAN) { + *out_msg << " boolean"; } - if (attr->type_mask & android::ResTable_map::TYPE_COLOR) { - *msg << " color"; + if (attr.type_mask & android::ResTable_map::TYPE_COLOR) { + *out_msg << " color"; } - if (attr->type_mask & android::ResTable_map::TYPE_DIMENSION) { - *msg << " dimension"; + if (attr.type_mask & android::ResTable_map::TYPE_DIMENSION) { + *out_msg << " dimension"; } - if (attr->type_mask & android::ResTable_map::TYPE_ENUM) { - *msg << " enum"; + if (attr.type_mask & android::ResTable_map::TYPE_ENUM) { + *out_msg << " enum"; } - if (attr->type_mask & android::ResTable_map::TYPE_FLAGS) { - *msg << " flags"; + if (attr.type_mask & android::ResTable_map::TYPE_FLAGS) { + *out_msg << " flags"; } - if (attr->type_mask & android::ResTable_map::TYPE_FLOAT) { - *msg << " float"; + if (attr.type_mask & android::ResTable_map::TYPE_FLOAT) { + *out_msg << " float"; } - if (attr->type_mask & android::ResTable_map::TYPE_FRACTION) { - *msg << " fraction"; + if (attr.type_mask & android::ResTable_map::TYPE_FRACTION) { + *out_msg << " fraction"; } - if (attr->type_mask & android::ResTable_map::TYPE_INTEGER) { - *msg << " integer"; + if (attr.type_mask & android::ResTable_map::TYPE_INTEGER) { + *out_msg << " integer"; } - if (attr->type_mask & android::ResTable_map::TYPE_REFERENCE) { - *msg << " reference"; + if (attr.type_mask & android::ResTable_map::TYPE_REFERENCE) { + *out_msg << " reference"; } - if (attr->type_mask & android::ResTable_map::TYPE_STRING) { - *msg << " string"; + if (attr.type_mask & android::ResTable_map::TYPE_STRING) { + *out_msg << " string"; } - *msg << " but got " << *value; + *out_msg << " but got " << value; } -bool Attribute::Matches(const Item* item, DiagMessage* out_msg) const { +bool Attribute::Matches(const Item& item, DiagMessage* out_msg) const { + constexpr const uint32_t TYPE_ENUM = android::ResTable_map::TYPE_ENUM; + constexpr const uint32_t TYPE_FLAGS = android::ResTable_map::TYPE_FLAGS; + constexpr const uint32_t TYPE_INTEGER = android::ResTable_map::TYPE_INTEGER; + constexpr const uint32_t TYPE_REFERENCE = android::ResTable_map::TYPE_REFERENCE; + android::Res_value val = {}; - item->Flatten(&val); + item.Flatten(&val); + + const uint32_t flattened_data = util::DeviceToHost32(val.data); // Always allow references. - const uint32_t mask = type_mask | android::ResTable_map::TYPE_REFERENCE; - if (!(mask & ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType))) { + const uint32_t actual_type = ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType); + + // Only one type must match between the actual and expected. + if ((actual_type & (type_mask | TYPE_REFERENCE)) == 0) { if (out_msg) { - BuildAttributeMismatchMessage(out_msg, this, item); + BuildAttributeMismatchMessage(*this, item, out_msg); } return false; + } + + // Enums and flags are encoded as integers, so check them first before doing any range checks. + if ((type_mask & TYPE_ENUM) != 0 && (actual_type & TYPE_ENUM) != 0) { + for (const Symbol& s : symbols) { + if (flattened_data == s.value) { + return true; + } + } + + // If the attribute accepts integers, we can't fail here. + if ((type_mask & TYPE_INTEGER) == 0) { + if (out_msg) { + *out_msg << item << " is not a valid enum"; + } + return false; + } + } + + if ((type_mask & TYPE_FLAGS) != 0 && (actual_type & TYPE_FLAGS) != 0) { + uint32_t mask = 0u; + for (const Symbol& s : symbols) { + mask |= s.value; + } + + // Check if the flattened data is covered by the flag bit mask. + // If the attribute accepts integers, we can't fail here. + if ((mask & flattened_data) == flattened_data) { + return true; + } else if ((type_mask & TYPE_INTEGER) == 0) { + if (out_msg) { + *out_msg << item << " is not a valid flag"; + } + return false; + } + } - } else if (ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType) & - android::ResTable_map::TYPE_INTEGER) { - if (static_cast<int32_t>(util::DeviceToHost32(val.data)) < min_int) { + // Finally check the integer range of the value. + if ((type_mask & TYPE_INTEGER) != 0 && (actual_type & TYPE_INTEGER) != 0) { + if (static_cast<int32_t>(flattened_data) < min_int) { if (out_msg) { - *out_msg << *item << " is less than minimum integer " << min_int; + *out_msg << item << " is less than minimum integer " << min_int; } return false; - } else if (static_cast<int32_t>(util::DeviceToHost32(val.data)) > max_int) { + } else if (static_cast<int32_t>(flattened_data) > max_int) { if (out_msg) { - *out_msg << *item << " is greater than maximum integer " << max_int; + *out_msg << item << " is greater than maximum integer " << max_int; } return false; } diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index ac5795fb9774..7e7547fc1b94 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -264,7 +264,7 @@ struct Attribute : public BaseValue<Attribute> { Attribute* Clone(StringPool* new_pool) const override; void PrintMask(std::ostream* out) const; void Print(std::ostream* out) const override; - bool Matches(const Item* item, DiagMessage* out_msg) const; + bool Matches(const Item& item, DiagMessage* out_msg = nullptr) const; }; struct Style : public BaseValue<Style> { diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp index 69f8e67530f6..06c3404561de 100644 --- a/tools/aapt2/ResourceValues_test.cpp +++ b/tools/aapt2/ResourceValues_test.cpp @@ -190,4 +190,52 @@ TEST(ResourcesValuesTest, EmptyReferenceFlattens) { EXPECT_EQ(0x0u, value.data); } +TEST(ResourcesValuesTest, AttributeMatches) { + constexpr const uint32_t TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION; + constexpr const uint32_t TYPE_ENUM = android::ResTable_map::TYPE_ENUM; + constexpr const uint32_t TYPE_FLAGS = android::ResTable_map::TYPE_FLAGS; + constexpr const uint32_t TYPE_INTEGER = android::ResTable_map::TYPE_INTEGER; + constexpr const uint8_t TYPE_INT_DEC = android::Res_value::TYPE_INT_DEC; + + Attribute attr1(false /*weak*/, TYPE_DIMENSION); + EXPECT_FALSE(attr1.Matches(*ResourceUtils::TryParseColor("#7fff00"))); + EXPECT_TRUE(attr1.Matches(*ResourceUtils::TryParseFloat("23dp"))); + EXPECT_TRUE(attr1.Matches(*ResourceUtils::TryParseReference("@android:string/foo"))); + + Attribute attr2(false /*weak*/, TYPE_INTEGER | TYPE_ENUM); + attr2.min_int = 0; + attr2.symbols.push_back(Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), + static_cast<uint32_t>(-1)}); + EXPECT_FALSE(attr2.Matches(*ResourceUtils::TryParseColor("#7fff00"))); + EXPECT_TRUE(attr2.Matches(BinaryPrimitive(TYPE_INT_DEC, static_cast<uint32_t>(-1)))); + EXPECT_TRUE(attr2.Matches(BinaryPrimitive(TYPE_INT_DEC, 1u))); + EXPECT_FALSE(attr2.Matches(BinaryPrimitive(TYPE_INT_DEC, static_cast<uint32_t>(-2)))); + + Attribute attr3(false /*weak*/, TYPE_INTEGER | TYPE_FLAGS); + attr3.max_int = 100; + attr3.symbols.push_back( + Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u}); + attr3.symbols.push_back( + Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x02u}); + attr3.symbols.push_back( + Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/baz")), 0x04u}); + attr3.symbols.push_back( + Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bat")), 0x80u}); + EXPECT_FALSE(attr3.Matches(*ResourceUtils::TryParseColor("#7fff00"))); + EXPECT_TRUE(attr3.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x01u | 0x02u))); + EXPECT_TRUE(attr3.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x01u | 0x02u | 0x80u))); + + // Not a flag, but a value less than max_int. + EXPECT_TRUE(attr3.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x08u))); + + // Not a flag and greater than max_int. + EXPECT_FALSE(attr3.Matches(BinaryPrimitive(TYPE_INT_DEC, 127u))); + + Attribute attr4(false /*weak*/, TYPE_ENUM); + attr4.symbols.push_back( + Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u}); + EXPECT_TRUE(attr4.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x01u))); + EXPECT_FALSE(attr4.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x02u))); +} + } // namespace aapt diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index e3745e8ce0da..5c32ed4fd849 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -52,6 +52,7 @@ enum : ApiVersion { SDK_NOUGAT = 24, SDK_NOUGAT_MR1 = 25, SDK_O = 26, + SDK_O_MR1 = 27, }; ApiVersion FindAttributeSdkLevel(const ResourceId& id); diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp index 57da5f01dcd1..705b1ab052af 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -27,7 +27,7 @@ #include "util/BigBuffer.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { @@ -75,9 +75,14 @@ const std::string* StringPool::Ref::operator->() const { return &entry_->value; } -const std::string& StringPool::Ref::operator*() const { return entry_->value; } +const std::string& StringPool::Ref::operator*() const { + return entry_->value; +} -size_t StringPool::Ref::index() const { return entry_->index; } +size_t StringPool::Ref::index() const { + // Account for the styles, which *always* come first. + return entry_->pool_->styles_.size() + entry_->index_; +} const StringPool::Context& StringPool::Ref::GetContext() const { return entry_->context; @@ -104,8 +109,7 @@ StringPool::StyleRef::~StyleRef() { } } -StringPool::StyleRef& StringPool::StyleRef::operator=( - const StringPool::StyleRef& rhs) { +StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) { if (rhs.entry_ != nullptr) { rhs.entry_->ref_++; } @@ -118,7 +122,7 @@ StringPool::StyleRef& StringPool::StyleRef::operator=( } bool StringPool::StyleRef::operator==(const StyleRef& rhs) const { - if (entry_->str != rhs.entry_->str) { + if (entry_->value != rhs.entry_->value) { return false; } @@ -137,7 +141,9 @@ bool StringPool::StyleRef::operator==(const StyleRef& rhs) const { return true; } -bool StringPool::StyleRef::operator!=(const StyleRef& rhs) const { return !operator==(rhs); } +bool StringPool::StyleRef::operator!=(const StyleRef& rhs) const { + return !operator==(rhs); +} const StringPool::StyleEntry* StringPool::StyleRef::operator->() const { return entry_; @@ -147,23 +153,24 @@ const StringPool::StyleEntry& StringPool::StyleRef::operator*() const { return *entry_; } -size_t StringPool::StyleRef::index() const { return entry_->str.index(); } +size_t StringPool::StyleRef::index() const { + return entry_->index_; +} const StringPool::Context& StringPool::StyleRef::GetContext() const { - return entry_->str.GetContext(); + return entry_->context; } StringPool::Ref StringPool::MakeRef(const StringPiece& str) { return MakeRefImpl(str, Context{}, true); } -StringPool::Ref StringPool::MakeRef(const StringPiece& str, - const Context& context) { +StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) { return MakeRefImpl(str, context, true); } -StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, - const Context& context, bool unique) { +StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context, + bool unique) { if (unique) { auto iter = indexed_strings_.find(str); if (iter != std::end(indexed_strings_)) { @@ -171,82 +178,87 @@ StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, } } - Entry* entry = new Entry(); + std::unique_ptr<Entry> entry(new Entry()); entry->value = str.to_string(); entry->context = context; - entry->index = strings_.size(); + entry->index_ = strings_.size(); entry->ref_ = 0; - strings_.emplace_back(entry); - indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry)); - return Ref(entry); + entry->pool_ = this; + + Entry* borrow = entry.get(); + strings_.emplace_back(std::move(entry)); + indexed_strings_.insert(std::make_pair(StringPiece(borrow->value), borrow)); + return Ref(borrow); } StringPool::StyleRef StringPool::MakeRef(const StyleString& str) { return MakeRef(str, Context{}); } -StringPool::StyleRef StringPool::MakeRef(const StyleString& str, - const Context& context) { - Entry* entry = new Entry(); +StringPool::StyleRef StringPool::MakeRef(const StyleString& str, const Context& context) { + std::unique_ptr<StyleEntry> entry(new StyleEntry()); entry->value = str.str; entry->context = context; - entry->index = strings_.size(); + entry->index_ = styles_.size(); entry->ref_ = 0; - strings_.emplace_back(entry); - indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry)); - - StyleEntry* style_entry = new StyleEntry(); - style_entry->str = Ref(entry); for (const aapt::Span& span : str.spans) { - style_entry->spans.emplace_back( - Span{MakeRef(span.name), span.first_char, span.last_char}); + entry->spans.emplace_back(Span{MakeRef(span.name), span.first_char, span.last_char}); } - style_entry->ref_ = 0; - styles_.emplace_back(style_entry); - return StyleRef(style_entry); + + StyleEntry* borrow = entry.get(); + styles_.emplace_back(std::move(entry)); + return StyleRef(borrow); } StringPool::StyleRef StringPool::MakeRef(const StyleRef& ref) { - Entry* entry = new Entry(); - entry->value = *ref.entry_->str; - entry->context = ref.entry_->str.entry_->context; - entry->index = strings_.size(); + std::unique_ptr<StyleEntry> entry(new StyleEntry()); + entry->value = ref.entry_->value; + entry->context = ref.entry_->context; + entry->index_ = styles_.size(); entry->ref_ = 0; - strings_.emplace_back(entry); - indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry)); - - StyleEntry* style_entry = new StyleEntry(); - style_entry->str = Ref(entry); for (const Span& span : ref.entry_->spans) { - style_entry->spans.emplace_back( - Span{MakeRef(*span.name), span.first_char, span.last_char}); + entry->spans.emplace_back(Span{MakeRef(*span.name), span.first_char, span.last_char}); + } + + StyleEntry* borrow = entry.get(); + styles_.emplace_back(std::move(entry)); + return StyleRef(borrow); +} + +void StringPool::ReAssignIndices() { + // Assign the style indices. + const size_t style_len = styles_.size(); + for (size_t index = 0; index < style_len; index++) { + styles_[index]->index_ = index; + } + + // Assign the string indices. + const size_t string_len = strings_.size(); + for (size_t index = 0; index < string_len; index++) { + strings_[index]->index_ = index; } - style_entry->ref_ = 0; - styles_.emplace_back(style_entry); - return StyleRef(style_entry); } void StringPool::Merge(StringPool&& pool) { - indexed_strings_.insert(pool.indexed_strings_.begin(), - pool.indexed_strings_.end()); - pool.indexed_strings_.clear(); - std::move(pool.strings_.begin(), pool.strings_.end(), - std::back_inserter(strings_)); - pool.strings_.clear(); - std::move(pool.styles_.begin(), pool.styles_.end(), - std::back_inserter(styles_)); + // First, change the owning pool for the incoming strings. + for (std::unique_ptr<Entry>& entry : pool.strings_) { + entry->pool_ = this; + } + + // Now move the styles, strings, and indices over. + std::move(pool.styles_.begin(), pool.styles_.end(), std::back_inserter(styles_)); pool.styles_.clear(); + std::move(pool.strings_.begin(), pool.strings_.end(), std::back_inserter(strings_)); + pool.strings_.clear(); + indexed_strings_.insert(pool.indexed_strings_.begin(), pool.indexed_strings_.end()); + pool.indexed_strings_.clear(); - // Assign the indices. - const size_t len = strings_.size(); - for (size_t index = 0; index < len; index++) { - strings_[index]->index = index; - } + ReAssignIndices(); } -void StringPool::HintWillAdd(size_t stringCount, size_t styleCount) { - strings_.reserve(strings_.size() + stringCount); - styles_.reserve(styles_.size() + styleCount); +void StringPool::HintWillAdd(size_t string_count, size_t style_count) { + strings_.reserve(strings_.size() + string_count); + styles_.reserve(styles_.size() + style_count); } void StringPool::Prune() { @@ -262,47 +274,42 @@ void StringPool::Prune() { auto end_iter2 = std::remove_if(strings_.begin(), strings_.end(), - [](const std::unique_ptr<Entry>& entry) -> bool { - return entry->ref_ <= 0; - }); - - auto end_iter3 = - std::remove_if(styles_.begin(), styles_.end(), - [](const std::unique_ptr<StyleEntry>& entry) -> bool { - return entry->ref_ <= 0; - }); - - // Remove the entries at the end or else we'll be accessing - // a deleted string from the StyleEntry. + [](const std::unique_ptr<Entry>& entry) -> bool { return entry->ref_ <= 0; }); + auto end_iter3 = std::remove_if( + styles_.begin(), styles_.end(), + [](const std::unique_ptr<StyleEntry>& entry) -> bool { return entry->ref_ <= 0; }); + + // Remove the entries at the end or else we'll be accessing a deleted string from the StyleEntry. strings_.erase(end_iter2, strings_.end()); styles_.erase(end_iter3, styles_.end()); - // Reassign the indices. - const size_t len = strings_.size(); - for (size_t index = 0; index < len; index++) { - strings_[index]->index = index; - } + ReAssignIndices(); } -void StringPool::Sort( - const std::function<bool(const Entry&, const Entry&)>& cmp) { - std::sort( - strings_.begin(), strings_.end(), - [&cmp](const std::unique_ptr<Entry>& a, - const std::unique_ptr<Entry>& b) -> bool { return cmp(*a, *b); }); - - // Assign the indices. - const size_t len = strings_.size(); - for (size_t index = 0; index < len; index++) { - strings_[index]->index = index; +template <typename E> +static void SortEntries( + std::vector<std::unique_ptr<E>>& entries, + const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) { + using UEntry = std::unique_ptr<E>; + + if (cmp != nullptr) { + std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool { + int r = cmp(a->context, b->context); + if (r == 0) { + r = a->value.compare(b->value); + } + return r < 0; + }); + } else { + std::sort(entries.begin(), entries.end(), + [](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; }); } +} - // Reorder the styles. - std::sort(styles_.begin(), styles_.end(), - [](const std::unique_ptr<StyleEntry>& lhs, - const std::unique_ptr<StyleEntry>& rhs) -> bool { - return lhs->str.index() < rhs->str.index(); - }); +void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) { + SortEntries(styles_, cmp); + SortEntries(strings_, cmp); + ReAssignIndices(); } template <typename T> @@ -327,60 +334,31 @@ static size_t EncodedLengthUnits(size_t length) { return length > kMaxSize ? 2 : 1; } -bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { - const size_t start_index = out->size(); - android::ResStringPool_header* header = - out->NextBlock<android::ResStringPool_header>(); - header->header.type = android::RES_STRING_POOL_TYPE; - header->header.headerSize = sizeof(*header); - header->stringCount = pool.size(); +static void EncodeString(const std::string& str, const bool utf8, BigBuffer* out) { if (utf8) { - header->flags |= android::ResStringPool_header::UTF8_FLAG; - } - - uint32_t* indices = - pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr; - - uint32_t* style_indices = nullptr; - if (!pool.styles_.empty()) { - header->styleCount = pool.styles_.back()->str.index() + 1; - style_indices = out->NextBlock<uint32_t>(header->styleCount); - } - - const size_t before_strings_index = out->size(); - header->stringsStart = before_strings_index - start_index; - - for (const auto& entry : pool) { - *indices = out->size() - before_strings_index; - indices++; + const std::string& encoded = str; + const ssize_t utf16_length = + utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size()); + CHECK(utf16_length >= 0); - if (utf8) { - const std::string& encoded = entry->value; - const ssize_t utf16_length = utf8_to_utf16_length( - reinterpret_cast<const uint8_t*>(entry->value.data()), - entry->value.size()); - CHECK(utf16_length >= 0); + const size_t total_size = EncodedLengthUnits<char>(utf16_length) + + EncodedLengthUnits<char>(encoded.length()) + encoded.size() + 1; - const size_t total_size = EncodedLengthUnits<char>(utf16_length) + - EncodedLengthUnits<char>(encoded.length()) + - encoded.size() + 1; + char* data = out->NextBlock<char>(total_size); - char* data = out->NextBlock<char>(total_size); - - // First encode the UTF16 string length. - data = EncodeLength(data, utf16_length); + // First encode the UTF16 string length. + data = EncodeLength(data, utf16_length); - // Now encode the size of the real UTF8 string. - data = EncodeLength(data, encoded.length()); - strncpy(data, encoded.data(), encoded.size()); + // Now encode the size of the real UTF8 string. + data = EncodeLength(data, encoded.length()); + strncpy(data, encoded.data(), encoded.size()); } else { - const std::u16string encoded = util::Utf8ToUtf16(entry->value); + const std::u16string encoded = util::Utf8ToUtf16(str); const ssize_t utf16_length = encoded.size(); // 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); @@ -395,31 +373,55 @@ bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { // The null-terminating character is already here due to the block of data // being set to 0s on allocation. } +} + +bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { + 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); + header->header.headerSize = util::HostToDevice16(sizeof(*header)); + header->stringCount = util::HostToDevice32(pool.size()); + header->styleCount = util::HostToDevice32(pool.styles_.size()); + if (utf8) { + header->flags |= android::ResStringPool_header::UTF8_FLAG; } - out->Align4(); + uint32_t* indices = pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr; + uint32_t* style_indices = + pool.styles_.size() != 0 ? out->NextBlock<uint32_t>(pool.styles_.size()) : nullptr; - if (!pool.styles_.empty()) { - const size_t before_styles_index = out->size(); - header->stylesStart = before_styles_index - start_index; + const size_t before_strings_index = out->size(); + header->stringsStart = before_strings_index - start_index; - size_t current_index = 0; - for (const auto& entry : pool.styles_) { - while (entry->str.index() > current_index) { - style_indices[current_index++] = out->size() - before_styles_index; + // Styles always come first. + for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) { + *indices++ = out->size() - before_strings_index; + EncodeString(entry->value, utf8, out); + } - uint32_t* span_offset = out->NextBlock<uint32_t>(); - *span_offset = android::ResStringPool_span::END; - } - style_indices[current_index++] = out->size() - before_styles_index; - - android::ResStringPool_span* span = - out->NextBlock<android::ResStringPool_span>(entry->spans.size()); - for (const auto& s : entry->spans) { - span->name.index = s.name.index(); - span->firstChar = s.first_char; - span->lastChar = s.last_char; - span++; + for (const std::unique_ptr<Entry>& entry : pool.strings_) { + *indices++ = out->size() - before_strings_index; + EncodeString(entry->value, utf8, out); + } + + out->Align4(); + + if (style_indices != nullptr) { + const size_t before_styles_index = out->size(); + header->stylesStart = util::HostToDevice32(before_styles_index - start_index); + + for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) { + *style_indices++ = out->size() - before_styles_index; + + if (!entry->spans.empty()) { + android::ResStringPool_span* span = + out->NextBlock<android::ResStringPool_span>(entry->spans.size()); + for (const Span& s : entry->spans) { + span->name.index = util::HostToDevice32(s.name.index()); + span->firstChar = util::HostToDevice32(s.first_char); + span->lastChar = util::HostToDevice32(s.last_char); + span++; + } } uint32_t* spanEnd = out->NextBlock<uint32_t>(); @@ -436,7 +438,7 @@ bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { memset(padding, 0xff, padding_length); out->Align4(); } - header->header.size = out->size() - start_index; + header->header.size = util::HostToDevice32(out->size() - start_index); return true; } diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index d1232a29b5aa..8350d0d09108 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -42,12 +42,16 @@ struct StyleString { std::vector<Span> spans; }; +// A StringPool for storing the value of String and StyledString resources. +// Styles and Strings are stored separately, since the runtime variant of this +// class -- ResStringPool -- requires that styled strings *always* appear first, since their +// style data is stored as an array indexed by the same indices as the main string pool array. +// Otherwise, the style data array would have to be sparse and take up more space. class StringPool { public: class Context { public: enum : uint32_t { - kStylePriority = 0u, kHighPriority = 1u, kNormalPriority = 0x7fffffffu, kLowPriority = 0xffffffffu, @@ -58,8 +62,8 @@ class StringPool { Context() = default; Context(uint32_t p, const ConfigDescription& c) : priority(p), config(c) {} explicit Context(uint32_t p) : priority(p) {} - explicit Context(const ConfigDescription& c) - : priority(kNormalPriority), config(c) {} + explicit Context(const ConfigDescription& c) : priority(kNormalPriority), config(c) { + } }; class Entry; @@ -116,13 +120,14 @@ class StringPool { public: std::string value; Context context; - size_t index; private: friend class StringPool; friend class Ref; + size_t index_; int ref_; + const StringPool* pool_; }; struct Span { @@ -133,18 +138,18 @@ class StringPool { class StyleEntry { public: - Ref str; + std::string value; + Context context; std::vector<Span> spans; private: friend class StringPool; friend class StyleRef; + size_t index_; int ref_; }; - using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator; - static bool FlattenUtf8(BigBuffer* out, const StringPool& pool); static bool FlattenUtf16(BigBuffer* out, const StringPool& pool); @@ -152,92 +157,61 @@ class StringPool { StringPool(StringPool&&) = default; StringPool& operator=(StringPool&&) = default; - /** - * Adds a string to the pool, unless it already exists. Returns - * a reference to the string in the pool. - */ + // Adds a string to the pool, unless it already exists. Returns a reference to the string in the + // pool. Ref MakeRef(const android::StringPiece& str); - /** - * Adds a string to the pool, unless it already exists, with a context - * object that can be used when sorting the string pool. Returns - * a reference to the string in the pool. - */ + // Adds a string to the pool, unless it already exists, with a context object that can be used + // when sorting the string pool. Returns a reference to the string in the pool. Ref MakeRef(const android::StringPiece& str, const Context& context); - /** - * Adds a style to the string pool and returns a reference to it. - */ + // Adds a style to the string pool and returns a reference to it. StyleRef MakeRef(const StyleString& str); - /** - * Adds a style to the string pool with a context object that - * can be used when sorting the string pool. Returns a reference - * to the style in the string pool. - */ + // Adds a style to the string pool with a context object that can be used when sorting the string + // pool. Returns a reference to the style in the string pool. StyleRef MakeRef(const StyleString& str, const Context& context); - /** - * Adds a style from another string pool. Returns a reference to the - * style in the string pool. - */ + // Adds a style from another string pool. Returns a reference to the style in the string pool. StyleRef MakeRef(const StyleRef& ref); - /** - * Moves pool into this one without coalescing strings. When this - * function returns, pool will be empty. - */ + // Moves pool into this one without coalescing strings. When this function returns, pool will be + // empty. void Merge(StringPool&& pool); - /** - * Returns the number of strings in the table. - */ - inline size_t size() const; + inline const std::vector<std::unique_ptr<Entry>>& strings() const { + return strings_; + } - /** - * Reserves space for strings and styles as an optimization. - */ + // Returns the number of strings in the table. + inline size_t size() const { + return styles_.size() + strings_.size(); + } + + // Reserves space for strings and styles as an optimization. void HintWillAdd(size_t string_count, size_t style_count); - /** - * Sorts the strings according to some comparison function. - */ - void Sort(const std::function<bool(const Entry&, const Entry&)>& cmp); + // Sorts the strings according to their Context using some comparison function. + // Equal Contexts are further sorted by string value, lexicographically. + // If no comparison function is provided, values are only sorted lexicographically. + void Sort(const std::function<int(const Context&, const Context&)>& cmp = nullptr); - /** - * Removes any strings that have no references. - */ + // Removes any strings that have no references. void Prune(); private: DISALLOW_COPY_AND_ASSIGN(StringPool); - friend const_iterator begin(const StringPool& pool); - friend const_iterator end(const StringPool& pool); - static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8); Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique); + void ReAssignIndices(); std::vector<std::unique_ptr<Entry>> strings_; std::vector<std::unique_ptr<StyleEntry>> styles_; std::unordered_multimap<android::StringPiece, Entry*> indexed_strings_; }; -// -// Inline implementation -// - -inline size_t StringPool::size() const { return strings_.size(); } - -inline StringPool::const_iterator begin(const StringPool& pool) { - return pool.strings_.begin(); -} - -inline StringPool::const_iterator end(const StringPool& pool) { - return pool.strings_.end(); -} - } // namespace aapt #endif // AAPT_STRING_POOL_H diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp index f64a8cf20928..b1e5ce2e28a8 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -23,8 +23,12 @@ #include "test/Test.h" #include "util/Util.h" -using android::StringPiece; -using android::StringPiece16; +using ::android::StringPiece; +using ::android::StringPiece16; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::NotNull; +using ::testing::Pointee; namespace aapt { @@ -32,129 +36,127 @@ TEST(StringPoolTest, InsertOneString) { StringPool pool; StringPool::Ref ref = pool.MakeRef("wut"); - EXPECT_EQ(*ref, "wut"); + EXPECT_THAT(*ref, Eq("wut")); } TEST(StringPoolTest, InsertTwoUniqueStrings) { StringPool pool; - StringPool::Ref ref = pool.MakeRef("wut"); - StringPool::Ref ref2 = pool.MakeRef("hey"); + StringPool::Ref ref_a = pool.MakeRef("wut"); + StringPool::Ref ref_b = pool.MakeRef("hey"); - EXPECT_EQ(*ref, "wut"); - EXPECT_EQ(*ref2, "hey"); + EXPECT_THAT(*ref_a, Eq("wut")); + EXPECT_THAT(*ref_b, Eq("hey")); } TEST(StringPoolTest, DoNotInsertNewDuplicateString) { StringPool pool; - StringPool::Ref ref = pool.MakeRef("wut"); - StringPool::Ref ref2 = pool.MakeRef("wut"); + StringPool::Ref ref_a = pool.MakeRef("wut"); + StringPool::Ref ref_b = pool.MakeRef("wut"); - EXPECT_EQ(*ref, "wut"); - EXPECT_EQ(*ref2, "wut"); - EXPECT_EQ(1u, pool.size()); + EXPECT_THAT(*ref_a, Eq("wut")); + EXPECT_THAT(*ref_b, Eq("wut")); + EXPECT_THAT(pool.size(), Eq(1u)); } TEST(StringPoolTest, MaintainInsertionOrderIndex) { StringPool pool; - StringPool::Ref ref = pool.MakeRef("z"); - StringPool::Ref ref2 = pool.MakeRef("a"); - StringPool::Ref ref3 = pool.MakeRef("m"); + StringPool::Ref ref_a = pool.MakeRef("z"); + StringPool::Ref ref_b = pool.MakeRef("a"); + StringPool::Ref ref_c = pool.MakeRef("m"); - EXPECT_EQ(0u, ref.index()); - EXPECT_EQ(1u, ref2.index()); - EXPECT_EQ(2u, ref3.index()); + EXPECT_THAT(ref_a.index(), Eq(0u)); + EXPECT_THAT(ref_b.index(), Eq(1u)); + EXPECT_THAT(ref_c.index(), Eq(2u)); } TEST(StringPoolTest, PruneStringsWithNoReferences) { StringPool pool; - StringPool::Ref refA = pool.MakeRef("foo"); + StringPool::Ref ref_a = pool.MakeRef("foo"); + + { + StringPool::Ref ref_b = pool.MakeRef("wut"); + EXPECT_THAT(*ref_b, Eq("wut")); + EXPECT_THAT(pool.size(), Eq(2u)); + pool.Prune(); + EXPECT_THAT(pool.size(), Eq(2u)); + } + EXPECT_THAT(pool.size(), Eq(2u)); + { - StringPool::Ref ref = pool.MakeRef("wut"); - EXPECT_EQ(*ref, "wut"); - EXPECT_EQ(2u, pool.size()); + StringPool::Ref ref_c = pool.MakeRef("bar"); + EXPECT_THAT(pool.size(), Eq(3u)); + + pool.Prune(); + EXPECT_THAT(pool.size(), Eq(2u)); } - StringPool::Ref refB = pool.MakeRef("bar"); + EXPECT_THAT(pool.size(), Eq(2u)); - EXPECT_EQ(3u, pool.size()); pool.Prune(); - EXPECT_EQ(2u, pool.size()); - StringPool::const_iterator iter = begin(pool); - EXPECT_EQ((*iter)->value, "foo"); - EXPECT_LT((*iter)->index, 2u); - ++iter; - EXPECT_EQ((*iter)->value, "bar"); - EXPECT_LT((*iter)->index, 2u); + EXPECT_THAT(pool.size(), Eq(1u)); } -TEST(StringPoolTest, SortAndMaintainIndexesInReferences) { +TEST(StringPoolTest, SortAndMaintainIndexesInStringReferences) { StringPool pool; - StringPool::Ref ref = pool.MakeRef("z"); - StringPool::StyleRef ref2 = pool.MakeRef(StyleString{{"a"}}); - StringPool::Ref ref3 = pool.MakeRef("m"); + StringPool::Ref ref_a = pool.MakeRef("z"); + StringPool::Ref ref_b = pool.MakeRef("a"); + StringPool::Ref ref_c = pool.MakeRef("m"); - EXPECT_EQ(*ref, "z"); - EXPECT_EQ(0u, ref.index()); + EXPECT_THAT(*ref_a, Eq("z")); + EXPECT_THAT(ref_a.index(), Eq(0u)); - EXPECT_EQ(*(ref2->str), "a"); - EXPECT_EQ(1u, ref2.index()); + EXPECT_THAT(*ref_b, Eq("a")); + EXPECT_THAT(ref_b.index(), Eq(1u)); - EXPECT_EQ(*ref3, "m"); - EXPECT_EQ(2u, ref3.index()); + EXPECT_THAT(*ref_c, Eq("m")); + EXPECT_THAT(ref_c.index(), Eq(2u)); - pool.Sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.value < b.value; - }); + pool.Sort(); - EXPECT_EQ(*ref, "z"); - EXPECT_EQ(2u, ref.index()); + EXPECT_THAT(*ref_a, Eq("z")); + EXPECT_THAT(ref_a.index(), Eq(2u)); - EXPECT_EQ(*(ref2->str), "a"); - EXPECT_EQ(0u, ref2.index()); + EXPECT_THAT(*ref_b, Eq("a")); + EXPECT_THAT(ref_b.index(), Eq(0u)); - EXPECT_EQ(*ref3, "m"); - EXPECT_EQ(1u, ref3.index()); + EXPECT_THAT(*ref_c, Eq("m")); + EXPECT_THAT(ref_c.index(), Eq(1u)); } TEST(StringPoolTest, SortAndStillDedupe) { StringPool pool; - StringPool::Ref ref = pool.MakeRef("z"); - StringPool::Ref ref2 = pool.MakeRef("a"); - StringPool::Ref ref3 = pool.MakeRef("m"); + StringPool::Ref ref_a = pool.MakeRef("z"); + StringPool::Ref ref_b = pool.MakeRef("a"); + StringPool::Ref ref_c = pool.MakeRef("m"); - pool.Sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.value < b.value; - }); + pool.Sort(); - StringPool::Ref ref4 = pool.MakeRef("z"); - StringPool::Ref ref5 = pool.MakeRef("a"); - StringPool::Ref ref6 = pool.MakeRef("m"); + StringPool::Ref ref_d = pool.MakeRef("z"); + StringPool::Ref ref_e = pool.MakeRef("a"); + StringPool::Ref ref_f = pool.MakeRef("m"); - EXPECT_EQ(ref4.index(), ref.index()); - EXPECT_EQ(ref5.index(), ref2.index()); - EXPECT_EQ(ref6.index(), ref3.index()); + EXPECT_THAT(ref_d.index(), Eq(ref_a.index())); + EXPECT_THAT(ref_e.index(), Eq(ref_b.index())); + EXPECT_THAT(ref_f.index(), Eq(ref_c.index())); } TEST(StringPoolTest, AddStyles) { StringPool pool; - StyleString str{{"android"}, {Span{{"b"}, 2, 6}}}; - - StringPool::StyleRef ref = pool.MakeRef(str); - - EXPECT_EQ(0u, ref.index()); - EXPECT_EQ(std::string("android"), *(ref->str)); - ASSERT_EQ(1u, ref->spans.size()); + StringPool::StyleRef ref = pool.MakeRef(StyleString{{"android"}, {Span{{"b"}, 2, 6}}}); + EXPECT_THAT(ref.index(), Eq(0u)); + EXPECT_THAT(ref->value, Eq("android")); + ASSERT_THAT(ref->spans.size(), Eq(1u)); const StringPool::Span& span = ref->spans.front(); - EXPECT_EQ(*(span.name), "b"); - EXPECT_EQ(2u, span.first_char); - EXPECT_EQ(6u, span.last_char); + EXPECT_THAT(*span.name, Eq("b")); + EXPECT_THAT(span.first_char, Eq(2u)); + EXPECT_THAT(span.last_char, Eq(6u)); } TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { @@ -163,9 +165,25 @@ TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { StringPool::Ref ref = pool.MakeRef("android"); StyleString str{{"android"}}; - StringPool::StyleRef styleRef = pool.MakeRef(str); + StringPool::StyleRef style_ref = pool.MakeRef(StyleString{{"android"}}); + + EXPECT_THAT(ref.index(), Ne(style_ref.index())); +} + +TEST(StringPoolTest, StylesAndStringsAreSeparateAfterSorting) { + StringPool pool; + + StringPool::StyleRef ref_a = pool.MakeRef(StyleString{{"beta"}}); + StringPool::Ref ref_b = pool.MakeRef("alpha"); + StringPool::StyleRef ref_c = pool.MakeRef(StyleString{{"alpha"}}); + + EXPECT_THAT(ref_b.index(), Ne(ref_c.index())); - EXPECT_NE(ref.index(), styleRef.index()); + pool.Sort(); + + EXPECT_THAT(ref_c.index(), Eq(0u)); + EXPECT_THAT(ref_a.index(), Eq(1u)); + EXPECT_THAT(ref_b.index(), Eq(2u)); } TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { @@ -177,7 +195,7 @@ TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { std::unique_ptr<uint8_t[]> data = util::Copy(buffer); ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); + ASSERT_THAT(test.setTo(data.get(), buffer.size()), Eq(NO_ERROR)); } TEST(StringPoolTest, FlattenOddCharactersUtf16) { @@ -193,9 +211,9 @@ TEST(StringPoolTest, FlattenOddCharactersUtf16) { ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); size_t len = 0; const char16_t* str = test.stringAt(0, &len); - EXPECT_EQ(1u, len); - EXPECT_EQ(u'\u093f', *str); - EXPECT_EQ(0u, str[1]); + EXPECT_THAT(len, Eq(1u)); + EXPECT_THAT(str, Pointee(Eq(u'\u093f'))); + EXPECT_THAT(str[1], Eq(0u)); } constexpr const char* sLongString = @@ -210,18 +228,20 @@ TEST(StringPoolTest, Flatten) { StringPool pool; - StringPool::Ref ref1 = pool.MakeRef("hello"); - StringPool::Ref ref2 = pool.MakeRef("goodbye"); - StringPool::Ref ref3 = pool.MakeRef(sLongString); - StringPool::Ref ref4 = pool.MakeRef(""); - StringPool::StyleRef ref5 = pool.MakeRef( - StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}}); + StringPool::Ref ref_a = pool.MakeRef("hello"); + StringPool::Ref ref_b = pool.MakeRef("goodbye"); + StringPool::Ref ref_c = pool.MakeRef(sLongString); + StringPool::Ref ref_d = pool.MakeRef(""); + StringPool::StyleRef ref_e = + pool.MakeRef(StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}}); + + // Styles are always first. + EXPECT_THAT(ref_e.index(), Eq(0u)); - EXPECT_EQ(0u, ref1.index()); - EXPECT_EQ(1u, ref2.index()); - EXPECT_EQ(2u, ref3.index()); - EXPECT_EQ(3u, ref4.index()); - EXPECT_EQ(4u, ref5.index()); + EXPECT_THAT(ref_a.index(), Eq(1u)); + EXPECT_THAT(ref_b.index(), Eq(2u)); + EXPECT_THAT(ref_c.index(), Eq(3u)); + EXPECT_THAT(ref_d.index(), Eq(4u)); BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; StringPool::FlattenUtf8(&buffers[0], pool); @@ -234,38 +254,37 @@ TEST(StringPoolTest, Flatten) { ResStringPool test; ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - EXPECT_EQ(std::string("hello"), util::GetString(test, 0)); - EXPECT_EQ(StringPiece16(u"hello"), util::GetString16(test, 0)); + EXPECT_THAT(util::GetString(test, 1), Eq("hello")); + EXPECT_THAT(util::GetString16(test, 1), Eq(u"hello")); - EXPECT_EQ(std::string("goodbye"), util::GetString(test, 1)); - EXPECT_EQ(StringPiece16(u"goodbye"), util::GetString16(test, 1)); + EXPECT_THAT(util::GetString(test, 2), Eq("goodbye")); + EXPECT_THAT(util::GetString16(test, 2), Eq(u"goodbye")); - EXPECT_EQ(StringPiece(sLongString), util::GetString(test, 2)); - EXPECT_EQ(util::Utf8ToUtf16(sLongString), util::GetString16(test, 2).to_string()); + 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(3, &len) != nullptr || - test.string8At(3, &len) != nullptr); - - EXPECT_EQ(std::string("style"), util::GetString(test, 4)); - EXPECT_EQ(StringPiece16(u"style"), util::GetString16(test, 4)); - - const ResStringPool_span* span = test.styleAt(4); - ASSERT_NE(nullptr, span); - EXPECT_EQ(std::string("b"), util::GetString(test, span->name.index)); - EXPECT_EQ(StringPiece16(u"b"), util::GetString16(test, span->name.index)); - EXPECT_EQ(0u, span->firstChar); - EXPECT_EQ(1u, span->lastChar); + EXPECT_TRUE(test.stringAt(4, &len) != nullptr || test.string8At(4, &len) != nullptr); + + 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()); + 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)); + EXPECT_THAT(span->lastChar, Eq(1u)); span++; - ASSERT_NE(ResStringPool_span::END, span->name.index); - EXPECT_EQ(std::string("i"), util::GetString(test, span->name.index)); - EXPECT_EQ(StringPiece16(u"i"), util::GetString16(test, span->name.index)); - EXPECT_EQ(2u, span->firstChar); - EXPECT_EQ(3u, span->lastChar); + ASSERT_THAT(span->name.index, Ne(ResStringPool_span::END)); + EXPECT_THAT(util::GetString(test, span->name.index), Eq("i")); + EXPECT_THAT(util::GetString16(test, span->name.index), Eq(u"i")); + EXPECT_THAT(span->firstChar, Eq(2u)); + EXPECT_THAT(span->lastChar, Eq(3u)); span++; - EXPECT_EQ(ResStringPool_span::END, span->name.index); + EXPECT_THAT(span->name.index, Eq(ResStringPool_span::END)); } } diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 740a401f9b57..5ad0cdd995ed 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -574,6 +574,11 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv if (file_op.xml_to_flatten) { std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = LinkAndVersionXmlFile(table, &file_op); + if (versioned_docs.empty()) { + error = true; + continue; + } + for (std::unique_ptr<xml::XmlResource>& doc : versioned_docs) { std::string dst_path = file_op.dst_path; if (doc->file.config != file_op.config) { @@ -989,7 +994,8 @@ class LinkCommand { manifest_class->GetCommentBuilder()->AppendComment(proper_annotation); } - const std::string& package_utf8 = context_->GetCompilationPackage(); + const std::string package_utf8 = + options_.custom_java_package.value_or_default(context_->GetCompilationPackage()); std::string out_path = options_.generate_java_class_path.value(); file::AppendPath(&out_path, file::PackageToPath(package_utf8)); diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 194c0c80c2b2..9d71775889d4 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -17,6 +17,7 @@ #include <memory> #include <vector> +#include "android-base/stringprintf.h" #include "androidfw/StringPiece.h" #include "Diagnostics.h" @@ -26,6 +27,8 @@ #include "SdkConstants.h" #include "ValueVisitor.h" #include "cmd/Util.h" +#include "configuration/ConfigurationParser.h" +#include "filter/AbiFilter.h" #include "flatten/TableFlattener.h" #include "flatten/XmlFlattener.h" #include "io/BigBufferInputStream.h" @@ -33,14 +36,21 @@ #include "optimize/ResourceDeduper.h" #include "optimize/VersionCollapser.h" #include "split/TableSplitter.h" +#include "util/Files.h" -using android::StringPiece; +using ::aapt::configuration::Abi; +using ::aapt::configuration::Artifact; +using ::aapt::configuration::PostProcessingConfiguration; +using ::android::StringPiece; +using ::android::base::StringPrintf; namespace aapt { struct OptimizeOptions { // Path to the output APK. - std::string output_path; + Maybe<std::string> output_path; + // Path to the output APK directory for splits. + Maybe<std::string> output_dir; // Details of the app extracted from the AndroidManifest.xml AppInfo app_info; @@ -55,6 +65,8 @@ struct OptimizeOptions { std::vector<SplitConstraints> split_constraints; TableFlattenerOptions table_flattener_options; + + Maybe<PostProcessingConfiguration> configuration; }; class OptimizeContext : public IAaptContext { @@ -175,10 +187,52 @@ class OptimizeCommand { ++split_constraints_iter; } - std::unique_ptr<IArchiveWriter> writer = - CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path); - if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) { - return 1; + if (options_.configuration && options_.output_dir) { + PostProcessingConfiguration& config = options_.configuration.value(); + + // For now, just write out the stripped APK since ABI splitting doesn't modify anything else. + for (const Artifact& artifact : config.artifacts) { + if (artifact.abi_group) { + const std::string& group = artifact.abi_group.value(); + + auto abi_group = config.abi_groups.find(group); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (abi_group == config.abi_groups.end()) { + context_->GetDiagnostics()->Note( + DiagMessage() << "could not find referenced ABI group '" << group << "'"); + return 1; + } + FilterChain filters; + filters.AddFilter(AbiFilter::FromAbiList(abi_group->second)); + + const std::string& path = apk->GetSource().path; + const StringPiece ext = file::GetExtension(path); + const std::string name = path.substr(0, path.rfind(ext.to_string())); + + // Name is hard coded for now since only one split dimension is supported. + // TODO: Incorporate name generation into the configuration objects. + const std::string file_name = + StringPrintf("%s.%s%s", name.c_str(), group.c_str(), ext.data()); + std::string out = options_.output_dir.value(); + file::AppendPath(&out, file_name); + + std::unique_ptr<IArchiveWriter> writer = + CreateZipFileArchiveWriter(context_->GetDiagnostics(), out); + + if (!apk->WriteToArchive(context_, options_.table_flattener_options, &filters, + writer.get())) { + return 1; + } + } + } + } + + if (options_.output_path) { + std::unique_ptr<IArchiveWriter> writer = + CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value()); + if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) { + return 1; + } } return 0; @@ -214,8 +268,8 @@ class OptimizeCommand { if (file_ref->file == nullptr) { ResourceNameRef name(pkg->name, type->type, entry->name); context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource()) - << "file for resource " << name << " with config '" - << config_value->config << "' not found"); + << "file for resource " << name << " with config '" + << config_value->config << "' not found"); continue; } @@ -293,13 +347,16 @@ bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk, int Optimize(const std::vector<StringPiece>& args) { OptimizeContext context; OptimizeOptions options; + Maybe<std::string> config_path; Maybe<std::string> target_densities; std::vector<std::string> configs; std::vector<std::string> split_args; bool verbose = false; Flags flags = Flags() - .RequiredFlag("-o", "Path to the output APK.", &options.output_path) + .OptionalFlag("-o", "Path to the output APK.", &options.output_path) + .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir) + .OptionalFlag("-x", "Path to XML configuration file.", &config_path) .OptionalFlag( "--target-densities", "Comma separated list of the screen densities that the APK will be optimized for.\n" @@ -369,6 +426,22 @@ int Optimize(const std::vector<StringPiece>& args) { } } + if (config_path) { + if (!options.output_dir) { + context.GetDiagnostics()->Error( + DiagMessage() << "Output directory is required when using a configuration file"); + return 1; + } + std::string& path = config_path.value(); + Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path); + if (for_path) { + options.configuration = for_path.value().WithDiagnostics(context.GetDiagnostics()).Parse(); + } else { + context.GetDiagnostics()->Error(DiagMessage() << "Could not parse config file " << path); + return 1; + } + } + if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) { return 1; } diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index 8741b7b678ec..e1c45d68f611 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -179,6 +179,13 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, xml::Attribute{"", "configForSplit", app_info.split_name.value()}); } + // Splits may contain more configurations than originally desired (fallback densities, etc.). + // This makes programmatic discovery of split targetting difficult. Encode the original + // split constraints intended for this split. + std::stringstream target_config_str; + target_config_str << util::Joiner(constraints.configs, ","); + manifest_el->attributes.push_back(xml::Attribute{"", "targetConfig", target_config_str.str()}); + std::unique_ptr<xml::Element> application_el = util::make_unique<xml::Element>(); application_el->name = "application"; application_el->attributes.push_back( diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp index d465091d224e..5cff0048c062 100644 --- a/tools/aapt2/compile/IdAssigner_test.cpp +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -64,12 +64,12 @@ TEST(IdAssignerTest, AssignIdsWithReservedIds) { // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX. maybe_result = table->FindResource(test::ParseNameOrDie("android:dimen/two")); - AAPT_ASSERT_TRUE(maybe_result); + ASSERT_TRUE(maybe_result); EXPECT_EQ(make_value<uint8_t>(2), maybe_result.value().type->id); maybe_result = table->FindResource(test::ParseNameOrDie("android:integer/three")); - AAPT_ASSERT_TRUE(maybe_result); + ASSERT_TRUE(maybe_result); EXPECT_EQ(make_value<uint8_t>(3), maybe_result.value().type->id); // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX @@ -77,17 +77,17 @@ TEST(IdAssignerTest, AssignIdsWithReservedIds) { maybe_result = table->FindResource(test::ParseNameOrDie("android:string/five")); - AAPT_ASSERT_TRUE(maybe_result); + ASSERT_TRUE(maybe_result); EXPECT_EQ(make_value<uint8_t>(5), maybe_result.value().type->id); // Expect to fill in the gaps between 0x01040000 and 0x01040006. maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/bar")); - AAPT_ASSERT_TRUE(maybe_result); + ASSERT_TRUE(maybe_result); EXPECT_EQ(make_value<uint16_t>(1), maybe_result.value().entry->id); maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/baz")); - AAPT_ASSERT_TRUE(maybe_result); + ASSERT_TRUE(maybe_result); EXPECT_EQ(make_value<uint16_t>(2), maybe_result.value().entry->id); } @@ -121,7 +121,7 @@ TEST(IdAssignerTest, AssignIdsWithIdMap) { ASSERT_TRUE(VerifyIds(table.get())); Maybe<ResourceTable::SearchResult> result = table->FindResource(test::ParseNameOrDie("android:attr/foo")); - AAPT_ASSERT_TRUE(result); + ASSERT_TRUE(result); const ResourceTable::SearchResult& search_result = result.value(); EXPECT_EQ(make_value<uint8_t>(0x01), search_result.package->id); diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index a031ea4c31ec..871ed4f01e77 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -120,7 +120,7 @@ std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, // All Span indices are UTF-16 based, according to the resources.arsc format expected by the // runtime. So we will do all our processing in UTF-16, then convert back. - const std::u16string text16 = util::Utf8ToUtf16(*string->value->str); + const std::u16string text16 = util::Utf8ToUtf16(string->value->value); // Convenient wrapper around the text that allows us to work with StringPieces. const StringPiece16 text(text16); diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp index b08e1dab35a9..711558aa51c1 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -31,7 +31,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), Pseudolocalizer::Method::kNone, &pool); - EXPECT_EQ(original_style.str, *new_string->value->str); + EXPECT_EQ(original_style.str, new_string->value->value); ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size()); EXPECT_EQ(std::string("i"), *new_string->value->spans[0].name); @@ -52,7 +52,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), Pseudolocalizer::Method::kAccent, &pool); - EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *new_string->value->str); + EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), new_string->value->value); ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size()); EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); @@ -79,7 +79,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentNestedTags) { Pseudolocalizer::Method::kAccent, &pool); ASSERT_NE(nullptr, new_string); ASSERT_EQ(2u, new_string->value->spans.size()); - EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str); + EXPECT_EQ(std::string("[ɓöļð one]"), new_string->value->value); EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); @@ -101,7 +101,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentTagsUnsorted) { Pseudolocalizer::Method::kAccent, &pool); ASSERT_NE(nullptr, new_string); ASSERT_EQ(2u, new_string->value->spans.size()); - EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str); + EXPECT_EQ(std::string("[ɓöļð one]"), new_string->value->value); EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); @@ -126,7 +126,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeNestedAndAdjacentTags) { ASSERT_EQ(4u, new_string->value->spans.size()); EXPECT_EQ(std::string( "[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļļ. one two three four five six]"), - *new_string->value->str); + new_string->value->value); EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš").size(), new_string->value->spans[0].first_char); @@ -165,7 +165,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizePartsOfString) { ASSERT_NE(nullptr, new_string); ASSERT_EQ(2u, new_string->value->spans.size()); EXPECT_EQ(std::string("[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžéð. one two three four]"), - *new_string->value->str); + new_string->value->value); EXPECT_EQ(std::string("em"), *new_string->value->spans[0].name); EXPECT_EQ(std::u16string(u"[Ţĥîš").size(), new_string->value->spans[0].first_char); @@ -265,7 +265,7 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) { ASSERT_NE(nullptr, new_styled_string); // "world" should be untranslated. - EXPECT_NE(std::string::npos, new_styled_string->value->str->find("world")); + EXPECT_NE(std::string::npos, new_styled_string->value->value.find("world")); String* new_string = test::GetValueForConfig<String>(table.get(), "android:string/bar", test::ParseConfigOrDie("en-rXA")); diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp index 15a3d8c289e2..3a515fad3202 100644 --- a/tools/aapt2/compile/Pseudolocalizer.cpp +++ b/tools/aapt2/compile/Pseudolocalizer.cpp @@ -445,9 +445,15 @@ std::string PseudoMethodBidi::Text(const StringPiece& source) { std::string result; bool lastspace = true; bool space = true; + bool escape = false; + const char ESCAPE_CHAR = '\\'; for (size_t i = 0; i < source.size(); i++) { char c = s[i]; - space = isspace(c); + if (!escape && c == ESCAPE_CHAR) { + escape = true; + continue; + } + space = (!escape && isspace(c)) || (escape && (c == 'n' || c == 't')); if (lastspace && !space) { // Word start result += kRlm + kRlo; @@ -456,6 +462,10 @@ std::string PseudoMethodBidi::Text(const StringPiece& source) { result += kPdf + kRlm; } lastspace = space; + if (escape) { + result.append(&ESCAPE_CHAR, 1); + escape=false; + } result.append(&c, 1); } if (!lastspace) { diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp index d3b7b02d1abb..65d2472ef2a9 100644 --- a/tools/aapt2/compile/Pseudolocalizer_test.cpp +++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp @@ -97,6 +97,11 @@ TEST(PseudolocalizerTest, PlaintextBidi) { "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE( + SimpleHelper("hello\\nworld\\n", + "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\\n" + "\xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\\n", + Pseudolocalizer::Method::kBidi)); } TEST(PseudolocalizerTest, SimpleICU) { diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index 89618d3a4676..d051120b9445 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -18,13 +18,18 @@ #include <algorithm> #include <functional> +#include <map> #include <memory> #include <utility> +#include <android-base/file.h> #include <android-base/logging.h> #include "ConfigDescription.h" #include "Diagnostics.h" +#include "io/File.h" +#include "io/FileSystem.h" +#include "util/Maybe.h" #include "util/Util.h" #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" @@ -38,10 +43,12 @@ using ::aapt::configuration::Abi; using ::aapt::configuration::AndroidManifest; using ::aapt::configuration::AndroidSdk; using ::aapt::configuration::Artifact; -using ::aapt::configuration::Configuration; +using ::aapt::configuration::PostProcessingConfiguration; using ::aapt::configuration::GlTexture; using ::aapt::configuration::Group; using ::aapt::configuration::Locale; +using ::aapt::io::IFile; +using ::aapt::io::RegularFile; using ::aapt::util::TrimWhitespace; using ::aapt::xml::Element; using ::aapt::xml::FindRootElement; @@ -49,16 +56,17 @@ using ::aapt::xml::NodeCast; using ::aapt::xml::XmlActionExecutor; using ::aapt::xml::XmlActionExecutorPolicy; using ::aapt::xml::XmlNodeAction; +using ::android::base::ReadFileToString; -const std::unordered_map<std::string, Abi> kAbiMap = { - {"armeabi", Abi::kArmeV6}, - {"armeabi-v7a", Abi::kArmV7a}, - {"arm64-v8a", Abi::kArm64V8a}, - {"x86", Abi::kX86}, - {"x86_64", Abi::kX86_64}, - {"mips", Abi::kMips}, - {"mips64", Abi::kMips64}, - {"universal", Abi::kUniversal}, +const std::unordered_map<std::string, Abi> kStringToAbiMap = { + {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a}, + {"x86", Abi::kX86}, {"x86_64", Abi::kX86_64}, {"mips", Abi::kMips}, + {"mips64", Abi::kMips64}, {"universal", Abi::kUniversal}, +}; +const std::map<Abi, std::string> kAbiToStringMap = { + {Abi::kArmeV6, "armeabi"}, {Abi::kArmV7a, "armeabi-v7a"}, {Abi::kArm64V8a, "arm64-v8a"}, + {Abi::kX86, "x86"}, {Abi::kX86_64, "x86_64"}, {Abi::kMips, "mips"}, + {Abi::kMips64, "mips64"}, {Abi::kUniversal, "universal"}, }; constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt"; @@ -96,12 +104,84 @@ class NamespaceVisitor : public xml::Visitor { } // namespace +namespace configuration { + +const std::string& AbiToString(Abi abi) { + return kAbiToStringMap.find(abi)->second; +} + +/** + * Attempts to replace the placeholder in the name string with the provided value. Returns true on + * success, or false if the either the placeholder is not found in the name, or the value is not + * present and the placeholder was. + */ +static bool ReplacePlaceholder(const std::string& placeholder, const Maybe<std::string>& value, + std::string* name, IDiagnostics* diag) { + size_t offset = name->find(placeholder); + if (value) { + if (offset == std::string::npos) { + diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder); + return false; + } + name->replace(offset, placeholder.length(), value.value()); + return true; + } + + // Make sure the placeholder was not present if the desired value was not present. + bool result = (offset == std::string::npos); + if (!result) { + diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder); + } + return result; +} + +Maybe<std::string> Artifact::ToArtifactName(const std::string& format, IDiagnostics* diag) const { + std::string result = format; + + if (!ReplacePlaceholder("{abi}", abi_group, &result, diag)) { + return {}; + } + + if (!ReplacePlaceholder("{density}", screen_density_group, &result, diag)) { + return {}; + } + + if (!ReplacePlaceholder("{locale}", locale_group, &result, diag)) { + return {}; + } + + if (!ReplacePlaceholder("{sdk}", android_sdk_group, &result, diag)) { + return {}; + } + + if (!ReplacePlaceholder("{feature}", device_feature_group, &result, diag)) { + return {}; + } + + if (!ReplacePlaceholder("{gl}", gl_texture_group, &result, diag)) { + return {}; + } + + return result; +} + +} // namespace configuration + +/** Returns a ConfigurationParser for the file located at the provided path. */ +Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) { + std::string contents; + if (!ReadFileToString(path, &contents, true)) { + return {}; + } + return ConfigurationParser(contents); +} + ConfigurationParser::ConfigurationParser(std::string contents) : contents_(std::move(contents)), diag_(&noop_) { } -Maybe<Configuration> ConfigurationParser::Parse() { +Maybe<PostProcessingConfiguration> ConfigurationParser::Parse() { std::istringstream in(contents_); auto doc = xml::Inflate(&in, diag_, Source("config.xml")); @@ -133,10 +213,11 @@ Maybe<Configuration> ConfigurationParser::Parse() { XmlNodeAction& artifacts_action = root_action["artifacts"]; XmlNodeAction& groups_action = root_action["groups"]; - Configuration config; + PostProcessingConfiguration config; // Helper to bind a static method to an action handler in the DOM executor. - auto bind_handler = [&config](std::function<bool(Configuration*, Element*, IDiagnostics*)> h) + auto bind_handler = + [&config](std::function<bool(PostProcessingConfiguration*, Element*, IDiagnostics*)> h) -> XmlNodeAction::ActionFuncWithDiag { return std::bind(h, &config, std::placeholders::_1, std::placeholders::_2); }; @@ -158,275 +239,266 @@ Maybe<Configuration> ConfigurationParser::Parse() { return {}; } + // TODO: Validate all references in the configuration are valid. It should be safe to assume from + // this point on that any references from one section to another will be present. + return {config}; } ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - Artifact artifact{}; - for (const auto& attr : root_element->attributes) { - if (attr.name == "name") { - artifact.name = attr.value; - } else if (attr.name == "abi-group") { - artifact.abi_group = {attr.value}; - } else if (attr.name == "screen-density-group") { - artifact.screen_density_group = {attr.value}; - } else if (attr.name == "locale-group") { - artifact.locale_group = {attr.value}; - } else if (attr.name == "android-sdk-group") { - artifact.android_sdk_group = {attr.value}; - } else if (attr.name == "gl-texture-group") { - artifact.gl_texture_group = {attr.value}; - } else if (attr.name == "device-feature-group") { - artifact.device_feature_group = {attr.value}; - } else { - diag->Note( - DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " << attr.value); - } - } - config->artifacts[artifact.name] = artifact; - return true; - }; + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + Artifact artifact{}; + for (const auto& attr : root_element->attributes) { + if (attr.name == "name") { + artifact.name = attr.value; + } else if (attr.name == "abi-group") { + artifact.abi_group = {attr.value}; + } else if (attr.name == "screen-density-group") { + artifact.screen_density_group = {attr.value}; + } else if (attr.name == "locale-group") { + artifact.locale_group = {attr.value}; + } else if (attr.name == "android-sdk-group") { + artifact.android_sdk_group = {attr.value}; + } else if (attr.name == "gl-texture-group") { + artifact.gl_texture_group = {attr.value}; + } else if (attr.name == "device-feature-group") { + artifact.device_feature_group = {attr.value}; + } else { + diag->Note(DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " + << attr.value); + } + } + config->artifacts.push_back(artifact); + return true; +}; ConfigurationParser::ActionHandler ConfigurationParser::artifact_format_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - for (auto& node : root_element->children) { - xml::Text* t; - if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - config->artifact_format = TrimWhitespace(t->text).to_string(); - break; - } - } - return true; - }; + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + for (auto& node : root_element->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + config->artifact_format = TrimWhitespace(t->text).to_string(); + break; + } + } + return true; +}; ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - auto& group = config->abi_groups[label]; - bool valid = true; + auto& group = config->abi_groups[label]; + bool valid = true; - for (auto* child : root_element->GetChildElements()) { - if (child->name != "abi") { - diag->Error( - DiagMessage() << "Unexpected element in ABI group: " << child->name); - valid = false; - } else { - for (auto& node : child->children) { - xml::Text* t; - if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - group.push_back(kAbiMap.at(TrimWhitespace(t->text).to_string())); - break; - } - } + for (auto* child : root_element->GetChildElements()) { + if (child->name != "abi") { + diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name); + valid = false; + } else { + for (auto& node : child->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string())); + break; } } + } + } - return valid; - }; + return valid; +}; ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - auto& group = config->screen_density_groups[label]; - bool valid = true; + auto& group = config->screen_density_groups[label]; + bool valid = true; - for (auto* child : root_element->GetChildElements()) { - if (child->name != "screen-density") { - diag->Error( - DiagMessage() << "Unexpected root_element in screen density group: " - << child->name); - valid = false; - } else { - for (auto& node : child->children) { - xml::Text* t; - if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - ConfigDescription config_descriptor; - const android::StringPiece& text = TrimWhitespace(t->text); - if (ConfigDescription::Parse(text, &config_descriptor)) { - // Copy the density with the minimum SDK version stripped out. - group.push_back(config_descriptor.CopyWithoutSdkVersion()); - } else { - diag->Error( - DiagMessage() << "Could not parse config descriptor for screen-density: " - << text); - valid = false; - } - break; - } + for (auto* child : root_element->GetChildElements()) { + if (child->name != "screen-density") { + diag->Error(DiagMessage() << "Unexpected root_element in screen density group: " + << child->name); + valid = false; + } else { + for (auto& node : child->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + ConfigDescription config_descriptor; + const android::StringPiece& text = TrimWhitespace(t->text); + if (ConfigDescription::Parse(text, &config_descriptor)) { + // Copy the density with the minimum SDK version stripped out. + group.push_back(config_descriptor.CopyWithoutSdkVersion()); + } else { + diag->Error(DiagMessage() + << "Could not parse config descriptor for screen-density: " << text); + valid = false; } + break; } } + } + } - return valid; - }; + return valid; +}; ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - auto& group = config->locale_groups[label]; - bool valid = true; + auto& group = config->locale_groups[label]; + bool valid = true; - for (auto* child : root_element->GetChildElements()) { - if (child->name != "locale") { - diag->Error( - DiagMessage() << "Unexpected root_element in screen density group: " - << child->name); - valid = false; + for (auto* child : root_element->GetChildElements()) { + if (child->name != "locale") { + diag->Error(DiagMessage() << "Unexpected root_element in screen density group: " + << child->name); + valid = false; + } else { + Locale entry; + for (const auto& attr : child->attributes) { + if (attr.name == "lang") { + entry.lang = {attr.value}; + } else if (attr.name == "region") { + entry.region = {attr.value}; } else { - Locale entry; - for (const auto& attr : child->attributes) { - if (attr.name == "lang") { - entry.lang = {attr.value}; - } else if (attr.name == "region") { - entry.region = {attr.value}; - } else { - diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name - << " = " << attr.value); - } - } - group.push_back(entry); + diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value); } } + group.push_back(entry); + } + } - return valid; - }; + return valid; +}; ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } - - auto& group = config->android_sdk_groups[label]; - bool valid = true; + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - for (auto* child : root_element->GetChildElements()) { - if (child->name != "android-sdk") { - diag->Error( - DiagMessage() << "Unexpected root_element in ABI group: " << child->name); - valid = false; + auto& group = config->android_sdk_groups[label]; + bool valid = true; + + for (auto* child : root_element->GetChildElements()) { + if (child->name != "android-sdk") { + diag->Error(DiagMessage() << "Unexpected root_element in ABI group: " << child->name); + valid = false; + } else { + AndroidSdk entry; + for (const auto& attr : child->attributes) { + if (attr.name == "minSdkVersion") { + entry.min_sdk_version = {attr.value}; + } else if (attr.name == "targetSdkVersion") { + entry.target_sdk_version = {attr.value}; + } else if (attr.name == "maxSdkVersion") { + entry.max_sdk_version = {attr.value}; } else { - AndroidSdk entry; - for (const auto& attr : child->attributes) { - if (attr.name == "minSdkVersion") { - entry.min_sdk_version = {attr.value}; - } else if (attr.name == "targetSdkVersion") { - entry.target_sdk_version = {attr.value}; - } else if (attr.name == "maxSdkVersion") { - entry.max_sdk_version = {attr.value}; - } else { - diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name - << " = " << attr.value); - } - } + diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value); + } + } - // TODO: Fill in the manifest details when they are finalised. - for (auto node : child->GetChildElements()) { - if (node->name == "manifest") { - if (entry.manifest) { - diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates."); - continue; - } - entry.manifest = {AndroidManifest()}; - } + // TODO: Fill in the manifest details when they are finalised. + for (auto node : child->GetChildElements()) { + if (node->name == "manifest") { + if (entry.manifest) { + diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates."); + continue; } - - group.push_back(entry); + entry.manifest = {AndroidManifest()}; } } - return valid; - }; + group.push_back(entry); + } + } + + return valid; +}; ConfigurationParser::ActionHandler ConfigurationParser::gl_texture_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - auto& group = config->gl_texture_groups[label]; - bool valid = true; + auto& group = config->gl_texture_groups[label]; + bool valid = true; - GlTexture result; - for (auto* child : root_element->GetChildElements()) { - if (child->name != "gl-texture") { - diag->Error( - DiagMessage() << "Unexpected element in GL texture group: " - << child->name); - valid = false; - } else { - for (const auto& attr : child->attributes) { - if (attr.name == "name") { - result.name = attr.value; - break; - } - } + GlTexture result; + for (auto* child : root_element->GetChildElements()) { + if (child->name != "gl-texture") { + diag->Error(DiagMessage() << "Unexpected element in GL texture group: " << child->name); + valid = false; + } else { + for (const auto& attr : child->attributes) { + if (attr.name == "name") { + result.name = attr.value; + break; + } + } - for (auto* element : child->GetChildElements()) { - if (element->name != "texture-path") { - diag->Error( - DiagMessage() << "Unexpected element in gl-texture element: " - << child->name); - valid = false; - continue; - } - for (auto& node : element->children) { - xml::Text* t; - if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - result.texture_paths.push_back(TrimWhitespace(t->text).to_string()); - } - } + for (auto* element : child->GetChildElements()) { + if (element->name != "texture-path") { + diag->Error(DiagMessage() << "Unexpected element in gl-texture element: " << child->name); + valid = false; + continue; + } + for (auto& node : element->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + result.texture_paths.push_back(TrimWhitespace(t->text).to_string()); } } - group.push_back(result); } + } + group.push_back(result); + } - return valid; - }; + return valid; +}; ConfigurationParser::ActionHandler ConfigurationParser::device_feature_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - auto& group = config->device_feature_groups[label]; - bool valid = true; + auto& group = config->device_feature_groups[label]; + bool valid = true; - for (auto* child : root_element->GetChildElements()) { - if (child->name != "supports-feature") { - diag->Error( - DiagMessage() << "Unexpected root_element in device feature group: " - << child->name); - valid = false; - } else { - for (auto& node : child->children) { - xml::Text* t; - if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - group.push_back(TrimWhitespace(t->text).to_string()); - break; - } - } + for (auto* child : root_element->GetChildElements()) { + if (child->name != "supports-feature") { + diag->Error(DiagMessage() << "Unexpected root_element in device feature group: " + << child->name); + valid = false; + } else { + for (auto& node : child->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + group.push_back(TrimWhitespace(t->text).to_string()); + break; } } + } + } - return valid; - }; + return valid; +}; } // namespace aapt diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h index 0fb2f714a76c..28c355e39643 100644 --- a/tools/aapt2/configuration/ConfigurationParser.h +++ b/tools/aapt2/configuration/ConfigurationParser.h @@ -20,8 +20,9 @@ #include <string> #include <unordered_map> #include <vector> -#include <ConfigDescription.h> +#include "ConfigDescription.h" +#include "Diagnostics.h" #include "util/Maybe.h" namespace aapt { @@ -48,6 +49,9 @@ struct Artifact { Maybe<std::string> device_feature_group; /** If present, uses the OpenGL texture group with this name. */ Maybe<std::string> gl_texture_group; + + /** Convert an artifact name template into a name string based on configuration contents. */ + Maybe<std::string> ToArtifactName(const std::string& format, IDiagnostics* diag) const; }; /** Enumeration of currently supported ABIs. */ @@ -62,6 +66,9 @@ enum class Abi { kUniversal }; +/** Helper method to convert an ABI to a string representing the path within the APK. */ +const std::string& AbiToString(Abi abi); + /** * Represents an individual locale. When a locale is included, it must be * declared from least specific to most specific, as a region does not make @@ -114,11 +121,10 @@ struct GlTexture { } }; -/** - * AAPT2 XML configuration binary representation. - */ -struct Configuration { - std::unordered_map<std::string, Artifact> artifacts; +/** AAPT2 XML configuration file binary representation. */ +struct PostProcessingConfiguration { + // TODO: Support named artifacts? + std::vector<Artifact> artifacts; Maybe<std::string> artifact_format; Group<Abi> abi_groups; @@ -142,18 +148,16 @@ class Element; */ class ConfigurationParser { public: + + /** Returns a ConfigurationParser for the file located at the provided path. */ + static Maybe<ConfigurationParser> ForPath(const std::string& path); + /** Returns a ConfigurationParser for the configuration in the provided file contents. */ static ConfigurationParser ForContents(const std::string& contents) { ConfigurationParser parser{contents}; return parser; } - /** Returns a ConfigurationParser for the file located at the provided path. */ - static ConfigurationParser ForPath(const std::string& path) { - // TODO: Read XML file into memory. - return ForContents(path); - } - /** Sets the diagnostics context to use when parsing. */ ConfigurationParser& WithDiagnostics(IDiagnostics* diagnostics) { diag_ = diagnostics; @@ -164,7 +168,7 @@ class ConfigurationParser { * Parses the configuration file and returns the results. If the configuration could not be parsed * the result is empty and any errors will be displayed with the provided diagnostics context. */ - Maybe<configuration::Configuration> Parse(); + Maybe<configuration::PostProcessingConfiguration> Parse(); protected: /** @@ -183,9 +187,8 @@ class ConfigurationParser { * An ActionHandler for processing XML elements in the XmlActionExecutor. Returns true if the * element was successfully processed, otherwise returns false. */ - using ActionHandler = std::function<bool(configuration::Configuration* config, - xml::Element* element, - IDiagnostics* diag)>; + using ActionHandler = std::function<bool(configuration::PostProcessingConfiguration* config, + xml::Element* element, IDiagnostics* diag)>; /** Handler for <artifact> tags. */ static ActionHandler artifact_handler_; @@ -213,4 +216,4 @@ class ConfigurationParser { } // namespace aapt -#endif //AAPT2_CONFIGURATION_H +#endif // AAPT2_CONFIGURATION_H diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index 72a97b273cb0..fb71e98d2fb5 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -32,7 +32,8 @@ namespace { using android::ResTable_config; using configuration::Abi; using configuration::AndroidSdk; -using configuration::Configuration; +using configuration::Artifact; +using configuration::PostProcessingConfiguration; using configuration::DeviceFeature; using configuration::GlTexture; using configuration::Locale; @@ -130,11 +131,16 @@ class ConfigurationParserTest : public ConfigurationParser, public ::testing::Te StdErrDiagnostics diag_; }; +TEST_F(ConfigurationParserTest, ForPath_NoFile) { + auto result = ConfigurationParser::ForPath("./does_not_exist.xml"); + EXPECT_FALSE(result); +} + TEST_F(ConfigurationParserTest, ValidateFile) { auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_); auto result = parser.Parse(); ASSERT_TRUE(result); - Configuration& value = result.value(); + PostProcessingConfiguration& value = result.value(); EXPECT_EQ(2ul, value.artifacts.size()); ASSERT_TRUE(value.artifact_format); EXPECT_EQ( @@ -185,13 +191,13 @@ TEST_F(ConfigurationParserTest, ArtifactAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); EXPECT_EQ(1ul, config.artifacts.size()); - auto& artifact = config.artifacts.begin()->second; + auto& artifact = config.artifacts.front(); EXPECT_EQ("", artifact.name); // TODO: make this fail. EXPECT_EQ("arm", artifact.abi_group.value()); EXPECT_EQ("large", artifact.screen_density_group.value()); @@ -199,6 +205,21 @@ TEST_F(ConfigurationParserTest, ArtifactAction) { EXPECT_EQ("19", artifact.android_sdk_group.value()); EXPECT_EQ("dxt1", artifact.gl_texture_group.value()); EXPECT_EQ("low-latency", artifact.device_feature_group.value()); + + // Perform a second action to ensure we get 2 artifacts. + static constexpr const char* second = R"xml( + <artifact + abi-group="other" + screen-density-group="large" + locale-group="europe" + android-sdk-group="19" + gl-texture-group="dxt1" + device-feature-group="low-latency"/>)xml"; + doc = test::BuildXmlDom(second); + + ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + ASSERT_TRUE(ok); + EXPECT_EQ(2ul, config.artifacts.size()); } TEST_F(ConfigurationParserTest, ArtifactFormatAction) { @@ -209,7 +230,7 @@ TEST_F(ConfigurationParserTest, ArtifactFormatAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = artifact_format_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); ASSERT_TRUE(config.artifact_format); @@ -232,7 +253,7 @@ TEST_F(ConfigurationParserTest, AbiGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = abi_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -255,7 +276,7 @@ TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = screen_density_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -285,7 +306,7 @@ TEST_F(ConfigurationParserTest, LocaleGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = locale_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -321,7 +342,7 @@ TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -353,7 +374,7 @@ TEST_F(ConfigurationParserTest, GlTextureGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = gl_texture_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -382,7 +403,7 @@ TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = device_feature_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -397,5 +418,55 @@ TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { ASSERT_THAT(out, ElementsAre(low_latency, pro)); } +TEST(ArtifactTest, Simple) { + StdErrDiagnostics diag; + Artifact x86; + x86.abi_group = {"x86"}; + + auto x86_result = x86.ToArtifactName("something.{abi}.apk", &diag); + ASSERT_TRUE(x86_result); + EXPECT_EQ(x86_result.value(), "something.x86.apk"); + + Artifact arm; + arm.abi_group = {"armeabi-v7a"}; + + auto arm_result = arm.ToArtifactName("app.{abi}.apk", &diag); + ASSERT_TRUE(arm_result); + EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); +} + +TEST(ArtifactTest, Complex) { + StdErrDiagnostics diag; + Artifact artifact; + artifact.abi_group = {"mips64"}; + artifact.screen_density_group = {"ldpi"}; + artifact.device_feature_group = {"df1"}; + artifact.gl_texture_group = {"glx1"}; + artifact.locale_group = {"en-AU"}; + artifact.android_sdk_group = {"26"}; + + auto result = + artifact.ToArtifactName("app.{density}_{locale}_{feature}_{gl}.sdk{sdk}.{abi}.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.sdk26.mips64.apk"); +} + +TEST(ArtifactTest, Missing) { + StdErrDiagnostics diag; + Artifact x86; + x86.abi_group = {"x86"}; + + EXPECT_FALSE(x86.ToArtifactName("something.{density}.apk", &diag)); + EXPECT_FALSE(x86.ToArtifactName("something.apk", &diag)); +} + +TEST(ArtifactTest, Empty) { + StdErrDiagnostics diag; + Artifact artifact; + + EXPECT_FALSE(artifact.ToArtifactName("something.{density}.apk", &diag)); + EXPECT_TRUE(artifact.ToArtifactName("something.apk", &diag)); +} + } // namespace } // namespace aapt diff --git a/tools/aapt2/configuration/example/config.xml b/tools/aapt2/configuration/example/config.xml index a8360f870b71..ce31e61b2d62 100644 --- a/tools/aapt2/configuration/example/config.xml +++ b/tools/aapt2/configuration/example/config.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" ?> -<post-process xmlns="http://schemas.android.com/tools/aapt2"> +<post-process xmlns="http://schemas.android.com/tools/aapt"> <groups> <abi-group label="arm"> <abi>armeabi-v7a</abi> diff --git a/tools/aapt2/filter/AbiFilter.cpp b/tools/aapt2/filter/AbiFilter.cpp new file mode 100644 index 000000000000..cb96235f98f9 --- /dev/null +++ b/tools/aapt2/filter/AbiFilter.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AbiFilter.h" + +#include <memory> + +#include "io/Util.h" + +namespace aapt { + +std::unique_ptr<AbiFilter> AbiFilter::FromAbiList(const std::vector<configuration::Abi>& abi_list) { + std::unordered_set<std::string> abi_set; + for (auto& abi : abi_list) { + abi_set.insert(configuration::AbiToString(abi)); + } + // Make unique by hand as the constructor is private. + return std::unique_ptr<AbiFilter>(new AbiFilter(abi_set)); +} + +bool AbiFilter::Keep(const std::string& path) { + // We only care about libraries. + if (!util::StartsWith(path, kLibPrefix)) { + return true; + } + + auto abi_end = path.find('/', kLibPrefixLen); + if (abi_end == std::string::npos) { + // Ignore any files in the top level lib directory. + return true; + } + + // Strip the lib/ prefix. + const std::string& path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen); + return (abis_.find(path_abi) != abis_.end()); +} + +} // namespace aapt diff --git a/tools/aapt2/filter/AbiFilter.h b/tools/aapt2/filter/AbiFilter.h new file mode 100644 index 000000000000..d875cb2b127b --- /dev/null +++ b/tools/aapt2/filter/AbiFilter.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT2_ABISPLITTER_H +#define AAPT2_ABISPLITTER_H + +#include <memory> +#include <string> +#include <unordered_set> +#include <vector> + +#include "configuration/ConfigurationParser.h" +#include "filter/Filter.h" + +namespace aapt { + +/** + * Filters native library paths by ABI. ABIs present in the filter list are kept and all over + * libraries are removed. The filter is only applied to native library paths (this under lib/). + */ +class AbiFilter : public IPathFilter { + public: + /** Factory method to create a filter from a list of configuration::Abi. */ + static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list); + + /** Returns true if the path is for a native library in the list of desired ABIs. */ + bool Keep(const std::string& path) override; + + private: + explicit AbiFilter(std::unordered_set<std::string> abis) : abis_(std::move(abis)) { + } + + /** The path prefix to where all native libs end up inside an APK file. */ + static constexpr const char* kLibPrefix = "lib/"; + static constexpr size_t kLibPrefixLen = 4; + const std::unordered_set<std::string> abis_; +}; + +} // namespace aapt + +#endif // AAPT2_ABISPLITTER_H diff --git a/tools/aapt2/filter/AbiFilter_test.cpp b/tools/aapt2/filter/AbiFilter_test.cpp new file mode 100644 index 000000000000..0c8ea3575a29 --- /dev/null +++ b/tools/aapt2/filter/AbiFilter_test.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "filter/AbiFilter.h" + +#include <string> + +#include "gtest/gtest.h" + +namespace aapt { +namespace { + +using ::aapt::configuration::Abi; + +struct TestData { + std::string path; + bool kept; +}; + +const TestData kTestData[] = { + /* Keep. */ + {"lib/mips/libnative.so", true}, + {"not/native/file.txt", true}, + // Not sure if this is a valid use case. + {"lib/listing.txt", true}, + {"lib/mips/foo/bar/baz.so", true}, + {"lib/mips/x86/foo.so", true}, + /* Discard. */ + {"lib/mips_horse/foo.so", false}, + {"lib/horse_mips/foo.so", false}, + {"lib/mips64/armeabi-v7a/foo.so", false}, + {"lib/mips64/x86_64/x86.so", false}, + {"lib/x86/libnative.so", false}, + {"lib/x86/foo/bar/baz.so", false}, + {"lib/x86/x86/foo.so", false}, + {"lib/x86_horse/foo.so", false}, + {"lib/horse_x86/foo.so", false}, + {"lib/x86/armeabi-v7a/foo.so", false}, + {"lib/x86_64/x86_64/x86.so", false}, +}; + +class AbiFilterTest : public ::testing::TestWithParam<TestData> {}; + +TEST_P(AbiFilterTest, Keep) { + auto mips = AbiFilter::FromAbiList({Abi::kMips}); + const TestData& data = GetParam(); + EXPECT_EQ(mips->Keep(data.path), data.kept); +} + +INSTANTIATE_TEST_CASE_P(NativePaths, AbiFilterTest, ::testing::ValuesIn(kTestData)); + +} // namespace +} // namespace aapt diff --git a/tools/aapt2/filter/Filter.h b/tools/aapt2/filter/Filter.h new file mode 100644 index 000000000000..d737dc92e87b --- /dev/null +++ b/tools/aapt2/filter/Filter.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT2_FILTER_H +#define AAPT2_FILTER_H + +#include <string> +#include <vector> + +#include "util/Util.h" + +namespace aapt { + +/** A filter to be applied to a path segment. */ +class IPathFilter { + public: + ~IPathFilter() = default; + + /** Returns true if the path should be kept. */ + virtual bool Keep(const std::string& path) = 0; +}; + +/** + * Path filter that keeps anything that matches the provided prefix. + */ +class PrefixFilter : public IPathFilter { + public: + explicit PrefixFilter(std::string prefix) : prefix_(std::move(prefix)) { + } + + /** Returns true if the provided path matches the prefix. */ + bool Keep(const std::string& path) override { + return util::StartsWith(path, prefix_); + } + + private: + const std::string prefix_; +}; + +/** Applies a set of IPathFilters to a path and returns true iif all filters keep the path. */ +class FilterChain : public IPathFilter { + public: + /** Adds a filter to the list to be applied to each path. */ + void AddFilter(std::unique_ptr<IPathFilter> filter) { + filters_.push_back(std::move(filter)); + } + + /** Returns true if all filters keep the path. */ + bool Keep(const std::string& path) override { + for (auto& filter : filters_) { + if (!filter->Keep(path)) { + return false; + } + } + return true; + } + + private: + std::vector<std::unique_ptr<IPathFilter>> filters_; +}; + +} // namespace aapt + +#endif // AAPT2_FILTER_H diff --git a/tools/aapt2/filter/Filter_test.cpp b/tools/aapt2/filter/Filter_test.cpp new file mode 100644 index 000000000000..fb75a4b4d7c1 --- /dev/null +++ b/tools/aapt2/filter/Filter_test.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "filter/Filter.h" + +#include <string> + +#include "io/Util.h" + +#include "gtest/gtest.h" + +namespace aapt { +namespace { + +TEST(FilterChainTest, EmptyChain) { + FilterChain chain; + ASSERT_TRUE(chain.Keep("some/random/path")); +} + +TEST(FilterChainTest, SingleFilter) { + FilterChain chain; + chain.AddFilter(util::make_unique<PrefixFilter>("keep/")); + + ASSERT_FALSE(chain.Keep("removed/path")); + ASSERT_TRUE(chain.Keep("keep/path/1")); + ASSERT_TRUE(chain.Keep("keep/path/2")); +} + +TEST(FilterChainTest, MultipleFilters) { + FilterChain chain; + chain.AddFilter(util::make_unique<PrefixFilter>("keep/")); + chain.AddFilter(util::make_unique<PrefixFilter>("keep/really/")); + + ASSERT_FALSE(chain.Keep("removed/path")); + ASSERT_FALSE(chain.Keep("/keep/really/wrong/prefix")); + ASSERT_FALSE(chain.Keep("keep/maybe/1")); + ASSERT_TRUE(chain.Keep("keep/really/1")); +} + +} // namespace +} // namespace aapt diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index f4d02262f25c..e5993a65366d 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -557,19 +557,15 @@ class PackageFlattener { } // namespace bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { - // We must do this before writing the resources, since the string pool IDs may - // change. - table->string_pool.Sort( - [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - int diff = a.context.priority - b.context.priority; - if (diff < 0) return true; - if (diff > 0) return false; - diff = a.context.config.compare(b.context.config); - if (diff < 0) return true; - if (diff > 0) return false; - return a.value < b.value; - }); + // We must do this before writing the resources, since the string pool IDs may change. table->string_pool.Prune(); + table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int { + int diff = util::compare(a.priority, b.priority); + if (diff == 0) { + diff = a.config.compare(b.config); + } + return diff; + }); // Write the ResTable header. ChunkWriter table_writer(buffer_); diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp index 6d1350d433a4..4fdb2eced59b 100644 --- a/tools/aapt2/flatten/TableFlattener_test.cpp +++ b/tools/aapt2/flatten/TableFlattener_test.cpp @@ -26,6 +26,9 @@ using namespace android; +using ::testing::IsNull; +using ::testing::NotNull; + namespace aapt { class TableFlattenerTest : public ::testing::Test { @@ -235,13 +238,12 @@ TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) { ResourceTable result; ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result)); - Attribute* actualAttr = - test::GetValue<Attribute>(&result, "android:attr/foo"); - ASSERT_NE(nullptr, actualAttr); - EXPECT_EQ(attr.IsWeak(), actualAttr->IsWeak()); - EXPECT_EQ(attr.type_mask, actualAttr->type_mask); - EXPECT_EQ(attr.min_int, actualAttr->min_int); - EXPECT_EQ(attr.max_int, actualAttr->max_int); + Attribute* actual_attr = test::GetValue<Attribute>(&result, "android:attr/foo"); + ASSERT_THAT(actual_attr, NotNull()); + EXPECT_EQ(attr.IsWeak(), actual_attr->IsWeak()); + EXPECT_EQ(attr.type_mask, actual_attr->type_mask); + EXPECT_EQ(attr.min_int, actual_attr->min_int); + EXPECT_EQ(attr.max_int, actual_attr->max_int); } static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries( @@ -303,15 +305,13 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) { auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0", sparse_config); - ASSERT_NE(nullptr, value); + ASSERT_THAT(value, NotNull()); EXPECT_EQ(0u, value->value.data); - ASSERT_EQ(nullptr, test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1", - sparse_config)); + ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1", sparse_config), IsNull()); - value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4", - sparse_config); - ASSERT_NE(nullptr, value); + value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4", sparse_config); + ASSERT_THAT(value, NotNull()); EXPECT_EQ(4u, value->value.data); } @@ -372,7 +372,7 @@ TEST_F(TableFlattenerTest, FlattenSharedLibrary) { Maybe<ResourceTable::SearchResult> search_result = result.FindResource(test::ParseNameOrDie("lib:id/foo")); - AAPT_ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result); EXPECT_EQ(0x00u, search_result.value().package->id.value()); auto iter = result.included_packages_.find(0x00); @@ -398,7 +398,7 @@ TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) { ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1); - ASSERT_NE(nullptr, dynamic_ref_table); + ASSERT_THAT(dynamic_ref_table, NotNull()); const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries(); @@ -423,7 +423,7 @@ TEST_F(TableFlattenerTest, PackageWithNonStandardIdHasDynamicRefTable) { ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1); - ASSERT_NE(nullptr, dynamic_ref_table); + ASSERT_THAT(dynamic_ref_table, NotNull()); const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries(); ssize_t idx = entries.indexOfKey(android::String16("app")); diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index bfebedef2a1e..331ef784a7da 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -180,8 +180,7 @@ class XmlFlattenerVisitor : public xml::Visitor { flatNode->lineNumber = util::HostToDevice32(node->line_number); flatNode->comment.index = util::HostToDevice32(-1); - ResXMLTree_namespaceExt* flat_ns = - writer.NextBlock<ResXMLTree_namespaceExt>(); + ResXMLTree_namespaceExt* flat_ns = writer.NextBlock<ResXMLTree_namespaceExt>(); AddString(node->namespace_prefix, kLowPriority, &flat_ns->prefix); AddString(node->namespace_uri, kLowPriority, &flat_ns->uri); @@ -289,8 +288,7 @@ class XmlFlattenerVisitor : public xml::Visitor { BigBuffer* buffer_; XmlFlattenerOptions options_; - // Scratch vector to filter attributes. We avoid allocations - // making this a member. + // Scratch vector to filter attributes. We avoid allocations making this a member. std::vector<xml::Attribute*> filtered_attrs_; }; @@ -307,10 +305,9 @@ bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) { } // Sort the string pool so that attribute resource IDs show up first. - visitor.pool.Sort( - [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.context.priority < b.context.priority; - }); + visitor.pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int { + return util::compare(a.priority, b.priority); + }); // Now we flatten the string pool references into the correct places. for (const auto& ref_entry : visitor.string_refs) { @@ -328,15 +325,13 @@ bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) { // Write the array of resource IDs, indexed by StringPool order. ChunkWriter res_id_map_writer(buffer_); res_id_map_writer.StartChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE); - for (const auto& str : visitor.pool) { - ResourceId id = {str->context.priority}; - if (id.id == kLowPriority || !id.is_valid()) { - // When we see the first non-resource ID, - // we're done. + for (const auto& str : visitor.pool.strings()) { + ResourceId id(str->context.priority); + if (str->context.priority == kLowPriority || !id.is_valid()) { + // When we see the first non-resource ID, we're done. break; } - - *res_id_map_writer.NextBlock<uint32_t>() = id.id; + *res_id_map_writer.NextBlock<uint32_t>() = util::HostToDevice32(id.id); } res_id_map_writer.Finish(); } diff --git a/tools/aapt2/integration-tests/AppOne/res/values/styles.xml b/tools/aapt2/integration-tests/AppOne/res/values/styles.xml index f05845cfab28..19d96c0809db 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values/styles.xml +++ b/tools/aapt2/integration-tests/AppOne/res/values/styles.xml @@ -25,6 +25,7 @@ <style name="Pop"> <item name="custom">@android:drawable/btn_default</item> <item name="android:focusable">true</item> + <item name="android:numColumns">auto_fit</item> </style> <string name="yo">@string/wow</string> diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp index a0ef00b1ea1f..1f83fa098d74 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -18,12 +18,29 @@ #include <algorithm> +#include "text/Unicode.h" +#include "text/Utf8Iterator.h" #include "util/Util.h" -using android::StringPiece; +using ::aapt::text::Utf8Iterator; +using ::android::StringPiece; namespace aapt { +StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment) { + Utf8Iterator iter(comment); + while (iter.HasNext()) { + const char32_t codepoint = iter.Next(); + if (codepoint == U'.') { + const size_t current_position = iter.Position(); + if (!iter.HasNext() || text::IsWhitespace(iter.Next())) { + return comment.substr(0, current_position); + } + } + } + return comment; +} + void AnnotationProcessor::AppendCommentLine(std::string& comment) { static const std::string sDeprecated = "@deprecated"; static const std::string sSystemApi = "@SystemApi"; diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index 99cd44fd2cc1..a06eda0f9c5c 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -53,6 +53,8 @@ namespace aapt { */ class AnnotationProcessor { public: + static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment); + /** * Adds more comments. Since resources can have various values with different * configurations, diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp index 3e43c4295c07..9ccac8888426 100644 --- a/tools/aapt2/java/AnnotationProcessor_test.cpp +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -18,6 +18,10 @@ #include "test/Test.h" +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::Not; + namespace aapt { TEST(AnnotationProcessorTest, EmitsDeprecated) { @@ -33,7 +37,7 @@ TEST(AnnotationProcessorTest, EmitsDeprecated) { processor.WriteToStream(&result, ""); std::string annotations = result.str(); - EXPECT_NE(std::string::npos, annotations.find("@Deprecated")); + EXPECT_THAT(annotations, HasSubstr("@Deprecated")); } TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { @@ -44,10 +48,20 @@ TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { processor.WriteToStream(&result, ""); std::string annotations = result.str(); - EXPECT_NE(std::string::npos, - annotations.find("@android.annotation.SystemApi")); - EXPECT_EQ(std::string::npos, annotations.find("@SystemApi")); - EXPECT_NE(std::string::npos, annotations.find("This is a system API")); + EXPECT_THAT(annotations, HasSubstr("@android.annotation.SystemApi")); + EXPECT_THAT(annotations, Not(HasSubstr("@SystemApi"))); + EXPECT_THAT(annotations, HasSubstr("This is a system API")); +} + +TEST(AnnotationProcessor, ExtractsFirstSentence) { + EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence("This is the only sentence"), + Eq("This is the only sentence")); + EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence( + "This is the\n first sentence. This is the rest of the paragraph."), + Eq("This is the\n first sentence.")); + EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence( + "This is the first sentence with a {@link android.R.styleable.Theme}."), + Eq("This is the first sentence with a {@link android.R.styleable.Theme}.")); } } // namespace aapt diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 2a23aa9e5372..44fa0f19a0e5 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -299,24 +299,16 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res } const ResourceName& attr_name = entry.attr_ref->name.value(); - styleable_comment << "<tr><td>"; - styleable_comment << "<code>{@link #" << entry.field_name << " " - << (!attr_name.package.empty() - ? attr_name.package - : context_->GetCompilationPackage()) - << ":" << attr_name.entry << "}</code>"; - styleable_comment << "</td>"; - - styleable_comment << "<td>"; + styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " " + << (!attr_name.package.empty() ? attr_name.package + : context_->GetCompilationPackage()) + << ":" << attr_name.entry << "}</code></td>"; // Only use the comment up until the first '.'. This is to stay compatible with // the way old AAPT did it (presumably to keep it short and to avoid including // annotations like @hide which would affect this Styleable). - auto iter = std::find(attr_comment_line.begin(), attr_comment_line.end(), '.'); - if (iter != attr_comment_line.end()) { - attr_comment_line = attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1); - } - styleable_comment << attr_comment_line << "</td></tr>\n"; + styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line) + << "</td></tr>\n"; } styleable_comment << "</table>\n"; diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index 5ebf508807e8..9f6ec210a6a7 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -18,124 +18,115 @@ #include "test/Test.h" -namespace aapt { +using ::testing::HasSubstr; +using ::testing::Not; -static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, - xml::XmlResource* res, - std::string* out_str) { - std::unique_ptr<ClassDefinition> manifest_class = - GenerateManifestClass(context->GetDiagnostics(), res); - if (!manifest_class) { - return ::testing::AssertionFailure() << "manifest_class == nullptr"; - } - - std::stringstream out; - if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true, - &out)) { - return ::testing::AssertionFailure() << "failed to write java file"; - } +namespace aapt { - *out_str = out.str(); - return ::testing::AssertionSuccess(); -} +static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res, + std::string* out_str); TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"EOF( - <manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <permission android:name="android.permission.ACCESS_INTERNET" /> - <permission android:name="android.DO_DANGEROUS_THINGS" /> - <permission android:name="com.test.sample.permission.HUH" /> - <permission-group android:name="foo.bar.PERMISSION" /> - </manifest>)EOF"); + std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <permission android:name="android.permission.ACCESS_INTERNET" /> + <permission android:name="android.DO_DANGEROUS_THINGS" /> + <permission android:name="com.test.sample.permission.HUH" /> + <permission-group android:name="foo.bar.PERMISSION" /> + </manifest>)"); std::string actual; ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual)); - const size_t permission_class_pos = - actual.find("public static final class permission {"); - const size_t permission_croup_class_pos = + ASSERT_THAT(actual, HasSubstr("public static final class permission {")); + ASSERT_THAT(actual, HasSubstr("public static final class permission_group {")); + + const size_t permission_start_pos = actual.find("public static final class permission {"); + const size_t permission_group_start_pos = actual.find("public static final class permission_group {"); - ASSERT_NE(std::string::npos, permission_class_pos); - ASSERT_NE(std::string::npos, permission_croup_class_pos); // // Make sure these permissions are in the permission class. // - - size_t pos = actual.find( - "public static final String ACCESS_INTERNET=" - "\"android.permission.ACCESS_INTERNET\";"); - EXPECT_GT(pos, permission_class_pos); - EXPECT_LT(pos, permission_croup_class_pos); - - pos = actual.find( - "public static final String DO_DANGEROUS_THINGS=" - "\"android.DO_DANGEROUS_THINGS\";"); - EXPECT_GT(pos, permission_class_pos); - EXPECT_LT(pos, permission_croup_class_pos); - - pos = actual.find( - "public static final String HUH=\"com.test.sample.permission.HUH\";"); - EXPECT_GT(pos, permission_class_pos); - EXPECT_LT(pos, permission_croup_class_pos); + const std::string permission_class = + actual.substr(permission_start_pos, permission_group_start_pos - permission_start_pos); + + EXPECT_THAT( + permission_class, + HasSubstr( + "public static final String ACCESS_INTERNET=\"android.permission.ACCESS_INTERNET\";")); + EXPECT_THAT( + permission_class, + HasSubstr("public static final String DO_DANGEROUS_THINGS=\"android.DO_DANGEROUS_THINGS\";")); + EXPECT_THAT(permission_class, + HasSubstr("public static final String HUH=\"com.test.sample.permission.HUH\";")); // // Make sure these permissions are in the permission_group class // + const std::string permission_group_class = actual.substr(permission_group_start_pos); - pos = actual.find( - "public static final String PERMISSION=" - "\"foo.bar.PERMISSION\";"); - EXPECT_GT(pos, permission_croup_class_pos); - EXPECT_LT(pos, std::string::npos); + EXPECT_THAT(permission_group_class, + HasSubstr("public static final String PERMISSION=\"foo.bar.PERMISSION\";")); } TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"EOF( - <manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- Required to access the internet. - Added in API 1. --> - <permission android:name="android.permission.ACCESS_INTERNET" /> - <!-- @deprecated This permission is for playing outside. --> - <permission android:name="android.permission.PLAY_OUTSIDE" /> - <!-- This is a private permission for system only! - @hide - @SystemApi --> - <permission android:name="android.permission.SECRET" /> - </manifest>)EOF"); + std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Required to access the internet. + Added in API 1. --> + <permission android:name="android.permission.ACCESS_INTERNET" /> + <!-- @deprecated This permission is for playing outside. --> + <permission android:name="android.permission.PLAY_OUTSIDE" /> + <!-- This is a private permission for system only! + @hide + @SystemApi --> + <permission android:name="android.permission.SECRET" /> + </manifest>)"); std::string actual; ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual)); - const char* expected_access_internet = - R"EOF( /** + const char* expected_access_internet = R"( /** * Required to access the internet. * Added in API 1. */ - public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF"; - - EXPECT_NE(std::string::npos, actual.find(expected_access_internet)); + public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)"; + EXPECT_THAT(actual, HasSubstr(expected_access_internet)); - const char* expected_play_outside = - R"EOF( /** + const char* expected_play_outside = R"( /** * @deprecated This permission is for playing outside. */ @Deprecated - public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF"; - - EXPECT_NE(std::string::npos, actual.find(expected_play_outside)); + public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)"; + EXPECT_THAT(actual, HasSubstr(expected_play_outside)); - const char* expected_secret = - R"EOF( /** + const char* expected_secret = R"( /** * This is a private permission for system only! * @hide */ @android.annotation.SystemApi - public static final String SECRET="android.permission.SECRET";)EOF"; + public static final String SECRET="android.permission.SECRET";)"; + EXPECT_THAT(actual, HasSubstr(expected_secret)); +} - EXPECT_NE(std::string::npos, actual.find(expected_secret)); +static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res, + std::string* out_str) { + std::unique_ptr<ClassDefinition> manifest_class = + GenerateManifestClass(context->GetDiagnostics(), res); + if (!manifest_class) { + return ::testing::AssertionFailure() << "manifest_class == nullptr"; + } + + std::stringstream out; + if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true, &out)) { + return ::testing::AssertionFailure() << "failed to write java file"; + } + + *out_str = out.str(); + return ::testing::AssertionSuccess(); } } // namespace aapt diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 624a559c4dae..5f61faeeebe7 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -85,7 +85,11 @@ class LayoutVisitor : public BaseVisitor { bool check_class = false; bool check_name = false; if (node->namespace_uri.empty()) { - check_class = node->name == "view" || node->name == "fragment"; + if (node->name == "view") { + check_class = true; + } else if (node->name == "fragment") { + check_class = check_name = true; + } } else if (node->namespace_uri == xml::kSchemaAndroid) { check_name = node->name == "fragment"; } @@ -110,6 +114,32 @@ class LayoutVisitor : public BaseVisitor { DISALLOW_COPY_AND_ASSIGN(LayoutVisitor); }; +class MenuVisitor : public BaseVisitor { + public: + MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + } + + virtual void Visit(xml::Element* node) override { + if (node->namespace_uri.empty() && node->name == "item") { + for (const auto& attr : node->attributes) { + if (attr.namespace_uri == xml::kSchemaAndroid) { + if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") && + util::IsJavaClassName(attr.value)) { + AddClass(node->line_number, attr.value); + } else if (attr.name == "onClick") { + AddMethod(node->line_number, attr.value); + } + } + } + } + + BaseVisitor::Visit(node); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MenuVisitor); +}; + class XmlResourceVisitor : public BaseVisitor { public: XmlResourceVisitor(const Source& source, KeepSet* keep_set) @@ -267,6 +297,12 @@ bool CollectProguardRules(const Source& source, xml::XmlResource* res, break; } + case ResourceType::kMenu: { + MenuVisitor visitor(source, keep_set); + res->root->Accept(&visitor); + break; + } + default: break; } diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp new file mode 100644 index 000000000000..900b07339715 --- /dev/null +++ b/tools/aapt2/java/ProguardRules_test.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "java/ProguardRules.h" + +#include "test/Test.h" + +using ::testing::HasSubstr; +using ::testing::Not; + +namespace aapt { + +TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <fragment xmlns:android="http://schemas.android.com/apk/res/android" + android:name="com.foo.Bar"/>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + + std::stringstream out; + ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + + std::string actual = out.str(); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); +} + +TEST(ProguardRulesTest, FragmentClassRuleIsEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = + test::BuildXmlDom(R"(<fragment class="com.foo.Bar"/>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + + std::stringstream out; + ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + + std::string actual = out.str(); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); +} + +TEST(ProguardRulesTest, FragmentNameAndClassRulesAreEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <fragment xmlns:android="http://schemas.android.com/apk/res/android" + android:name="com.foo.Baz" + class="com.foo.Bar"/>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + + std::stringstream out; + ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + + std::string actual = out.str(); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); + EXPECT_THAT(actual, HasSubstr("com.foo.Baz")); +} + +TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:onClick="bar_method" />)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + + std::stringstream out; + ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + + std::string actual = out.str(); + EXPECT_THAT(actual, HasSubstr("bar_method")); +} + +TEST(ProguardRulesTest, MenuRulesAreEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> menu = test::BuildXmlDom(R"( + <menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:onClick="on_click" + android:actionViewClass="com.foo.Bar" + android:actionProviderClass="com.foo.Baz" + android:name="com.foo.Bat" /> + </menu>)"); + menu->file.name = test::ParseNameOrDie("menu/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules({}, menu.get(), &set)); + + std::stringstream out; + ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + + std::string actual = out.str(); + EXPECT_THAT(actual, HasSubstr("on_click")); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); + EXPECT_THAT(actual, HasSubstr("com.foo.Baz")); + EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat"))); +} + +} // namespace aapt diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp index 755af0a1c6cc..49639f8ad549 100644 --- a/tools/aapt2/link/AutoVersioner_test.cpp +++ b/tools/aapt2/link/AutoVersioner_test.cpp @@ -19,43 +19,34 @@ #include "ConfigDescription.h" #include "test/Test.h" +using ::testing::NotNull; + namespace aapt { TEST(AutoVersionerTest, GenerateVersionedResources) { const ConfigDescription land_config = test::ParseConfigOrDie("land"); - const ConfigDescription sw600dp_land_config = - test::ParseConfigOrDie("sw600dp-land"); + const ConfigDescription sw600dp_land_config = test::ParseConfigOrDie("sw600dp-land"); ResourceEntry entry("foo"); - entry.values.push_back(util::make_unique<ResourceConfigValue>( - ConfigDescription::DefaultConfig(), "")); - entry.values.push_back( - util::make_unique<ResourceConfigValue>(land_config, "")); - entry.values.push_back( - util::make_unique<ResourceConfigValue>(sw600dp_land_config, "")); - - EXPECT_TRUE(ShouldGenerateVersionedResource( - &entry, ConfigDescription::DefaultConfig(), 17)); + entry.values.push_back(util::make_unique<ResourceConfigValue>(ConfigDescription::DefaultConfig(), "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(land_config, "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dp_land_config, "")); + + EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 17)); EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, land_config, 17)); } TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) { - const ConfigDescription sw600dp_v13_config = - test::ParseConfigOrDie("sw600dp-v13"); + const ConfigDescription sw600dp_v13_config = test::ParseConfigOrDie("sw600dp-v13"); const ConfigDescription v21_config = test::ParseConfigOrDie("v21"); ResourceEntry entry("foo"); - entry.values.push_back(util::make_unique<ResourceConfigValue>( - ConfigDescription::DefaultConfig(), "")); - entry.values.push_back( - util::make_unique<ResourceConfigValue>(sw600dp_v13_config, "")); - entry.values.push_back( - util::make_unique<ResourceConfigValue>(v21_config, "")); - - EXPECT_TRUE(ShouldGenerateVersionedResource( - &entry, ConfigDescription::DefaultConfig(), 17)); - EXPECT_FALSE(ShouldGenerateVersionedResource( - &entry, ConfigDescription::DefaultConfig(), 22)); + entry.values.push_back(util::make_unique<ResourceConfigValue>(ConfigDescription::DefaultConfig(), "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dp_v13_config, "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(v21_config, "")); + + EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 17)); + EXPECT_FALSE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 22)); } TEST(AutoVersionerTest, VersionStylesForTable) { @@ -92,46 +83,28 @@ TEST(AutoVersionerTest, VersionStylesForTable) { AutoVersioner versioner; ASSERT_TRUE(versioner.Consume(context.get(), table.get())); - Style* style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", - test::ParseConfigOrDie("v4")); - ASSERT_NE(style, nullptr); + Style* style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v4")); + ASSERT_THAT(style, NotNull()); ASSERT_EQ(style->entries.size(), 1u); - AAPT_ASSERT_TRUE(style->entries.front().key.name); - EXPECT_EQ(style->entries.front().key.name.value(), - test::ParseNameOrDie("android:attr/onClick")); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/onClick")), style->entries.front().key.name); - style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", - test::ParseConfigOrDie("v13")); - ASSERT_NE(style, nullptr); + style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v13")); + ASSERT_THAT(style, NotNull()); ASSERT_EQ(style->entries.size(), 2u); - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(style->entries[0].key.name.value(), - test::ParseNameOrDie("android:attr/onClick")); - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(style->entries[1].key.name.value(), - test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")); - - style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", - test::ParseConfigOrDie("v17")); - ASSERT_NE(style, nullptr); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/onClick")),style->entries[0].key.name); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")), style->entries[1].key.name); + + style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v17")); + ASSERT_THAT(style, NotNull()); ASSERT_EQ(style->entries.size(), 3u); - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(style->entries[0].key.name.value(), - test::ParseNameOrDie("android:attr/onClick")); - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(style->entries[1].key.name.value(), - test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")); - AAPT_ASSERT_TRUE(style->entries[2].key.name); - EXPECT_EQ(style->entries[2].key.name.value(), - test::ParseNameOrDie("android:attr/paddingStart")); - - style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", - test::ParseConfigOrDie("v21")); - ASSERT_NE(style, nullptr); - ASSERT_EQ(style->entries.size(), 1u); - AAPT_ASSERT_TRUE(style->entries.front().key.name); - EXPECT_EQ(style->entries.front().key.name.value(), - test::ParseNameOrDie("android:attr/paddingEnd")); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/onClick")), style->entries[0].key.name); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")), style->entries[1].key.name); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/paddingStart")), style->entries[2].key.name); + + style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v21")); + ASSERT_THAT(style, NotNull()); + ASSERT_EQ(1u, style->entries.size()); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/paddingEnd")), style->entries.front().key.name); } } // namespace aapt diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 012bb5ecdeca..6fb1793d90ed 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -310,6 +310,9 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["meta-data"] = meta_data_action; manifest_action["uses-split"].Action(RequiredNameIsJavaPackage); + manifest_action["key-sets"]["key-set"]["public-key"]; + manifest_action["key-sets"]["upgrade-key-set"]; + // Application actions. xml::XmlNodeAction& application_action = manifest_action["application"]; application_action.Action(OptionalNameIsJavaClassName); diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 064d3650065d..80edb352f42c 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -18,7 +18,8 @@ #include "test/Test.h" -using android::StringPiece; +using ::android::StringPiece; +using ::testing::NotNull; namespace aapt { @@ -420,4 +421,22 @@ TEST_F(ManifestFixerTest, DoNotIgnoreNonNamespacedElements) { EXPECT_EQ(nullptr, Verify(input)); } +TEST_F(ManifestFixerTest, SupportKeySets) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <key-sets> + <key-set android:name="old-set"> + <public-key android:name="old-key" android:value="some+old+key" /> + </key-set> + <key-set android:name="new-set"> + <public-key android:name="new-key" android:value="some+new+key" /> + </key-set> + <upgrade-key-set android:name="old-set" /> + <upgrade-key-set android:name="new-set" /> + </key-sets> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); +} + } // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index a8e510cd6140..414e56eb5dcc 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -109,18 +109,15 @@ class ReferenceLinkerVisitor : public ValueVisitor { entry.value->Accept(this); // Now verify that the type of this item is compatible with the - // attribute it - // is defined for. We pass `nullptr` as the DiagMessage so that this - // check is - // fast and we avoid creating a DiagMessage when the match is - // successful. - if (!symbol->attribute->Matches(entry.value.get(), nullptr)) { + // attribute it is defined for. We pass `nullptr` as the DiagMessage so that this + // check is fast and we avoid creating a DiagMessage when the match is successful. + if (!symbol->attribute->Matches(*entry.value, nullptr)) { // The actual type of this item is incompatible with the attribute. DiagMessage msg(entry.key.GetSource()); // Call the matches method again, this time with a DiagMessage so we // fill in the actual error message. - symbol->attribute->Matches(entry.value.get(), &msg); + symbol->attribute->Matches(*entry.value, &msg); context_->GetDiagnostics()->Error(msg); error_ = true; } diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp index d8e33a42711a..72a91689e392 100644 --- a/tools/aapt2/link/ReferenceLinker_test.cpp +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -19,6 +19,7 @@ #include "test/Test.h" using android::ResTable_map; +using ::testing::NotNull; namespace aapt { @@ -54,18 +55,18 @@ TEST(ReferenceLinkerTest, LinkSimpleReferences) { ASSERT_TRUE(linker.Consume(context.get(), table.get())); Reference* ref = test::GetValue<Reference>(table.get(), "com.app.test:string/foo"); - ASSERT_NE(nullptr, ref); - AAPT_ASSERT_TRUE(ref->id); + ASSERT_THAT(ref, NotNull()); + ASSERT_TRUE(ref->id); EXPECT_EQ(ResourceId(0x7f020001), ref->id.value()); ref = test::GetValue<Reference>(table.get(), "com.app.test:string/bar"); - ASSERT_NE(nullptr, ref); - AAPT_ASSERT_TRUE(ref->id); + ASSERT_THAT(ref, NotNull()); + ASSERT_TRUE(ref->id); EXPECT_EQ(ResourceId(0x7f020002), ref->id.value()); ref = test::GetValue<Reference>(table.get(), "com.app.test:string/baz"); - ASSERT_NE(nullptr, ref); - AAPT_ASSERT_TRUE(ref->id); + ASSERT_THAT(ref, NotNull()); + ASSERT_TRUE(ref->id); EXPECT_EQ(ResourceId(0x01040034), ref->id.value()); } @@ -84,10 +85,9 @@ TEST(ReferenceLinkerTest, LinkStyleAttributes) { { // We need to fill in the value for the attribute android:attr/bar after we - // build the - // table, because we need access to the string pool. + // build the table, because we need access to the string pool. Style* style = test::GetValue<Style>(table.get(), "com.app.test:style/Theme"); - ASSERT_NE(nullptr, style); + ASSERT_THAT(style, NotNull()); style->entries.back().value = util::make_unique<RawString>(table->string_pool.MakeRef("one|two")); } @@ -118,20 +118,20 @@ TEST(ReferenceLinkerTest, LinkStyleAttributes) { ASSERT_TRUE(linker.Consume(context.get(), table.get())); Style* style = test::GetValue<Style>(table.get(), "com.app.test:style/Theme"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().id); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); + ASSERT_TRUE(style->parent.value().id); EXPECT_EQ(ResourceId(0x01060000), style->parent.value().id.value()); ASSERT_EQ(2u, style->entries.size()); - AAPT_ASSERT_TRUE(style->entries[0].key.id); + ASSERT_TRUE(style->entries[0].key.id); EXPECT_EQ(ResourceId(0x01010001), style->entries[0].key.id.value()); - ASSERT_NE(nullptr, ValueCast<BinaryPrimitive>(style->entries[0].value.get())); + ASSERT_THAT(ValueCast<BinaryPrimitive>(style->entries[0].value.get()), NotNull()); - AAPT_ASSERT_TRUE(style->entries[1].key.id); + ASSERT_TRUE(style->entries[1].key.id); EXPECT_EQ(ResourceId(0x01010002), style->entries[1].key.id.value()); - ASSERT_NE(nullptr, ValueCast<BinaryPrimitive>(style->entries[1].value.get())); + ASSERT_THAT(ValueCast<BinaryPrimitive>(style->entries[1].value.get()), NotNull()); } TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { @@ -165,9 +165,9 @@ TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { ASSERT_TRUE(linker.Consume(context.get(), table.get())); Style* style = test::GetValue<Style>(table.get(), "com.app.test:style/Theme"); - ASSERT_NE(nullptr, style); + ASSERT_THAT(style, NotNull()); ASSERT_EQ(1u, style->entries.size()); - AAPT_ASSERT_TRUE(style->entries.front().key.id); + ASSERT_TRUE(style->entries.front().key.id); EXPECT_EQ(ResourceId(0x7f010000), style->entries.front().key.id.value()); } @@ -266,7 +266,7 @@ TEST(ReferenceLinkerTest, AppsWithSamePackageButDifferentIdAreVisibleNonPublic) const CallSite call_site{ResourceNameRef("com.app.test", ResourceType::kString, "foo")}; const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveSymbolCheckVisibility( *test::BuildReference("com.app.test:string/foo"), call_site, &table, &error); - ASSERT_NE(nullptr, symbol); + ASSERT_THAT(symbol, NotNull()); EXPECT_TRUE(error.empty()); } @@ -283,12 +283,12 @@ TEST(ReferenceLinkerTest, AppsWithDifferentPackageCanNotUseEachOthersAttribute) std::string error; const CallSite call_site{ResourceNameRef("com.app.ext", ResourceType::kLayout, "foo")}; - AAPT_EXPECT_FALSE(ReferenceLinker::CompileXmlAttribute( + EXPECT_FALSE(ReferenceLinker::CompileXmlAttribute( *test::BuildReference("com.app.test:attr/foo"), call_site, &table, &error)); EXPECT_FALSE(error.empty()); error = ""; - AAPT_ASSERT_TRUE(ReferenceLinker::CompileXmlAttribute( + ASSERT_TRUE(ReferenceLinker::CompileXmlAttribute( *test::BuildReference("com.app.test:attr/public_foo"), call_site, &table, &error)); EXPECT_TRUE(error.empty()); } diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index de81e73f613e..228cfb9e3d66 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -18,6 +18,9 @@ #include "test/Test.h" +using ::testing::IsNull; +using ::testing::NotNull; + namespace aapt { class XmlReferenceLinkerTest : public ::testing::Test { @@ -90,56 +93,48 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); xml::Element* view_el = xml::FindRootElement(doc.get()); - ASSERT_NE(nullptr, view_el); + ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "layout_width"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(ResourceId(0x01010000), xml_attr->compiled_attribute.value().id.value()); - ASSERT_NE(nullptr, xml_attr->compiled_value); - ASSERT_NE(nullptr, ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get())); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x01010000)), xml_attr->compiled_attribute.value().id); + EXPECT_THAT(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), NotNull()); xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "background"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(ResourceId(0x01010001), xml_attr->compiled_attribute.value().id.value()); - ASSERT_NE(nullptr, xml_attr->compiled_value); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x01010001)), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); - ASSERT_NE(nullptr, ref); - AAPT_ASSERT_TRUE(ref->name); - EXPECT_EQ(test::ParseNameOrDie("color/green"), ref->name.value()); // Make sure the name - // didn't change. - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ResourceId(0x7f020000), ref->id.value()); + ASSERT_THAT(ref, NotNull()); + EXPECT_EQ(make_value(test::ParseNameOrDie("color/green")), ref->name); // Make sure the name + // didn't change. + EXPECT_EQ(make_value(ResourceId(0x7f020000)), ref->id); xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "text"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - ASSERT_FALSE(xml_attr->compiled_value); // Strings don't get compiled for memory sake. + ASSERT_THAT(xml_attr, NotNull()); + EXPECT_TRUE(xml_attr->compiled_attribute); + EXPECT_THAT(xml_attr->compiled_value, IsNull()); // Strings don't get compiled for memory sake. xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "attr"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - ASSERT_FALSE(xml_attr->compiled_value); // Should be a plain string. + ASSERT_THAT(xml_attr, NotNull()); + EXPECT_TRUE(xml_attr->compiled_attribute); + EXPECT_THAT(xml_attr->compiled_value, IsNull()); // Should be a plain string. xml_attr = view_el->FindAttribute("", "nonAaptAttr"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_FALSE(xml_attr->compiled_attribute); - ASSERT_NE(nullptr, xml_attr->compiled_value); - ASSERT_NE(nullptr, ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get())); + ASSERT_THAT(xml_attr, NotNull()); + EXPECT_FALSE(xml_attr->compiled_attribute); + EXPECT_THAT(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), NotNull()); xml_attr = view_el->FindAttribute("", "nonAaptAttrRef"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_FALSE(xml_attr->compiled_attribute); - ASSERT_NE(nullptr, xml_attr->compiled_value); - ASSERT_NE(nullptr, ValueCast<Reference>(xml_attr->compiled_value.get())); + ASSERT_THAT(xml_attr, NotNull()); + EXPECT_FALSE(xml_attr->compiled_attribute); + EXPECT_THAT(ValueCast<Reference>(xml_attr->compiled_value.get()), NotNull()); xml_attr = view_el->FindAttribute("", "class"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_FALSE(xml_attr->compiled_attribute); - ASSERT_EQ(nullptr, xml_attr->compiled_value); + ASSERT_THAT(xml_attr, NotNull()); + EXPECT_FALSE(xml_attr->compiled_attribute); + EXPECT_THAT(xml_attr->compiled_value, IsNull()); } TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) { @@ -169,15 +164,14 @@ TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); xml::Element* view_el = xml::FindRootElement(doc.get()); - ASSERT_NE(view_el, nullptr); + ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.android.support"), "colorAccent"); - ASSERT_NE(xml_attr, nullptr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), ResourceId(0x7f010001)); - ASSERT_NE(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), nullptr); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x7f010001)), xml_attr->compiled_attribute.value().id); + EXPECT_THAT(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), NotNull()); } TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { @@ -189,18 +183,16 @@ TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); xml::Element* view_el = xml::FindRootElement(doc.get()); - ASSERT_NE(view_el, nullptr); + ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAuto, "colorAccent"); - ASSERT_NE(xml_attr, nullptr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), ResourceId(0x7f010000)); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x7f010000)), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->name); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); + ASSERT_THAT(ref, NotNull()); + ASSERT_TRUE(ref->name); + EXPECT_EQ(make_value(ResourceId(0x7f020001)), ref->id); } TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { @@ -215,35 +207,31 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); xml::Element* view_el = xml::FindRootElement(doc.get()); - ASSERT_NE(view_el, nullptr); + ASSERT_THAT(view_el, NotNull()); // All attributes and references in this element should be referring to // "android" (0x01). xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "attr"); - ASSERT_NE(xml_attr, nullptr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), ResourceId(0x01010002)); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x01010002)), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x01030000)); + ASSERT_THAT(ref, NotNull()); + EXPECT_EQ(make_value(ResourceId(0x01030000)), ref->id); ASSERT_FALSE(view_el->GetChildElements().empty()); view_el = view_el->GetChildElements().front(); - ASSERT_NE(view_el, nullptr); + ASSERT_THAT(view_el, NotNull()); // All attributes and references in this element should be referring to // "com.app.test" (0x7f). xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"), "attr"); - ASSERT_NE(xml_attr, nullptr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), ResourceId(0x7f010002)); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x7f010002)), xml_attr->compiled_attribute.value().id); ref = ValueCast<Reference>(xml_attr->compiled_value.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); + ASSERT_THAT(ref, NotNull()); + EXPECT_EQ(make_value(ResourceId(0x7f030000)), ref->id); } TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { @@ -255,20 +243,17 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); xml::Element* view_el = xml::FindRootElement(doc.get()); - ASSERT_NE(view_el, nullptr); + ASSERT_THAT(view_el, NotNull()); // All attributes and references in this element should be referring to // "com.app.test" (0x7f). - xml::Attribute* xml_attr = - view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"), "attr"); - ASSERT_NE(xml_attr, nullptr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), ResourceId(0x7f010002)); + xml::Attribute* xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"), "attr"); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x7f010002)), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); + ASSERT_THAT(ref, NotNull()); + EXPECT_EQ(make_value(ResourceId(0x7f030000)), ref->id); } } // namespace aapt diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp index 38bf4e3bd8eb..6b21364b5eb2 100644 --- a/tools/aapt2/proto/ProtoHelpers.cpp +++ b/tools/aapt2/proto/ProtoHelpers.cpp @@ -18,8 +18,7 @@ namespace aapt { -void SerializeStringPoolToPb(const StringPool& pool, - pb::StringPool* out_pb_pool) { +void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool) { BigBuffer buffer(1024); StringPool::FlattenUtf8(&buffer, pool); @@ -28,14 +27,12 @@ void SerializeStringPoolToPb(const StringPool& pool, size_t offset = 0; for (const BigBuffer::Block& block : buffer) { - data->insert(data->begin() + offset, block.buffer.get(), - block.buffer.get() + block.size); + data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size); offset += block.size; } } -void SerializeSourceToPb(const Source& source, StringPool* src_pool, - pb::Source* out_pb_source) { +void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source) { StringPool::Ref ref = src_pool->MakeRef(source.path); out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index())); if (source.line) { @@ -43,8 +40,7 @@ void SerializeSourceToPb(const Source& source, StringPool* src_pool, } } -void DeserializeSourceFromPb(const pb::Source& pb_source, - const android::ResStringPool& src_pool, +void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStringPool& src_pool, Source* out_source) { if (pb_source.has_path_idx()) { out_source->path = util::GetString(src_pool, pb_source.path_idx()); @@ -80,8 +76,7 @@ SymbolState DeserializeVisibilityFromPb( return SymbolState::kUndefined; } -void SerializeConfig(const ConfigDescription& config, - pb::ConfigDescription* out_pb_config) { +void SerializeConfig(const ConfigDescription& config, pb::ConfigDescription* out_pb_config) { android::ResTable_config flat_config = config; flat_config.size = sizeof(flat_config); flat_config.swapHtoD(); @@ -99,8 +94,7 @@ bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config, return false; } - config = reinterpret_cast<const android::ResTable_config*>( - pb_config.data().data()); + config = reinterpret_cast<const android::ResTable_config*>(pb_config.data().data()); out_config->copyFromDtoH(*config); return true; } diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp index 4b5619235c06..37d5ed0cf59d 100644 --- a/tools/aapt2/proto/TableProtoDeserializer.cpp +++ b/tools/aapt2/proto/TableProtoDeserializer.cpp @@ -195,8 +195,7 @@ class PackagePbDeserializer { spans++; } return util::make_unique<StyledString>(pool->MakeRef( - style_str, - StringPool::Context(StringPool::Context::kStylePriority, config))); + style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); } return util::make_unique<String>( pool->MakeRef(str, StringPool::Context(config))); diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp index d87d64e1cb46..730442c62836 100644 --- a/tools/aapt2/proto/TableProtoSerializer.cpp +++ b/tools/aapt2/proto/TableProtoSerializer.cpp @@ -87,7 +87,9 @@ class PbSerializerVisitor : public RawValueVisitor { pb_prim->set_data(val.data); } - void VisitItem(Item* item) override { LOG(FATAL) << "unimplemented item"; } + void VisitItem(Item* item) override { + LOG(FATAL) << "unimplemented item"; + } void Visit(Attribute* attr) override { pb::Attribute* pb_attr = pb_compound_value()->mutable_attr(); @@ -207,19 +209,15 @@ class PbSerializerVisitor : public RawValueVisitor { } // namespace std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { - // We must do this before writing the resources, since the string pool IDs may - // change. - table->string_pool.Sort( - [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - int diff = a.context.priority - b.context.priority; - if (diff < 0) return true; - if (diff > 0) return false; - diff = a.context.config.compare(b.context.config); - if (diff < 0) return true; - if (diff > 0) return false; - return a.value < b.value; - }); + // We must do this before writing the resources, since the string pool IDs may change. table->string_pool.Prune(); + table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int { + int diff = util::compare(a.priority, b.priority); + if (diff == 0) { + diff = a.config.compare(b.config); + } + return diff; + }); auto pb_table = util::make_unique<pb::ResourceTable>(); SerializeStringPoolToPb(table->string_pool, pb_table->mutable_string_pool()); diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp index e6ce6d37b879..3ebb08eb791e 100644 --- a/tools/aapt2/proto/TableProtoSerializer_test.cpp +++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp @@ -20,6 +20,7 @@ #include "test/Test.h" using ::google::protobuf::io::StringOutputStream; +using ::testing::NotNull; namespace aapt { @@ -46,8 +47,7 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { // Make a plural. std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - plural->values[Plural::One] = - util::make_unique<String>(table->string_pool.MakeRef("one")); + plural->values[Plural::One] = util::make_unique<String>(table->string_pool.MakeRef("one")); ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"), ConfigDescription{}, {}, std::move(plural), context->GetDiagnostics())); @@ -77,19 +77,19 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { context->GetDiagnostics())); std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table.get()); - ASSERT_NE(nullptr, pb_table); + ASSERT_THAT(pb_table, NotNull()); std::unique_ptr<ResourceTable> new_table = DeserializeTableFromPb( *pb_table, Source{"test"}, context->GetDiagnostics()); - ASSERT_NE(nullptr, new_table); + ASSERT_THAT(new_table, NotNull()); Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo"); - ASSERT_NE(nullptr, new_id); + ASSERT_THAT(new_id, NotNull()); EXPECT_EQ(id->IsWeak(), new_id->IsWeak()); Maybe<ResourceTable::SearchResult> result = new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main")); - AAPT_ASSERT_TRUE(result); + ASSERT_TRUE(result); EXPECT_EQ(SymbolState::kPublic, result.value().type->symbol_status.state); EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state); @@ -101,18 +101,18 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { // Find the product-dependent values BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), ""); - ASSERT_NE(nullptr, prim); + ASSERT_THAT(prim, NotNull()); EXPECT_EQ(123u, prim->value.data); prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet"); - ASSERT_NE(nullptr, prim); + ASSERT_THAT(prim, NotNull()); EXPECT_EQ(321u, prim->value.data); Reference* actual_ref = test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc"); - ASSERT_NE(nullptr, actual_ref); - AAPT_ASSERT_TRUE(actual_ref->name); - AAPT_ASSERT_TRUE(actual_ref->id); + ASSERT_THAT(actual_ref, NotNull()); + ASSERT_TRUE(actual_ref->name); + ASSERT_TRUE(actual_ref->id); EXPECT_EQ(expected_ref.name.value(), actual_ref->name.value()); EXPECT_EQ(expected_ref.id.value(), actual_ref->id.value()); } @@ -159,7 +159,7 @@ TEST(TableProtoSerializer, SerializeFileHeader) { std::unique_ptr<ResourceFile> file = DeserializeCompiledFileFromPb( new_pb_file, Source("test"), context->GetDiagnostics()); - ASSERT_NE(nullptr, file); + ASSERT_THAT(file, NotNull()); uint64_t offset, len; ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); @@ -171,16 +171,14 @@ TEST(TableProtoSerializer, SerializeFileHeader) { EXPECT_EQ(0u, offset & 0x03); ASSERT_EQ(1u, file->exported_symbols.size()); - EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), - file->exported_symbols[0].name); + EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), file->exported_symbols[0].name); // Read the second compiled file. ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file)); - file = DeserializeCompiledFileFromPb(new_pb_file, Source("test"), - context->GetDiagnostics()); - ASSERT_NE(nullptr, file); + file = DeserializeCompiledFileFromPb(new_pb_file, Source("test"), context->GetDiagnostics()); + ASSERT_THAT(file, NotNull()); ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index 0290e30dfced..c8d36177beb4 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,9 +1,32 @@ # Android Asset Packaging Tool 2.0 (AAPT2) release notes +## Version 2.18 +### `aapt2 ...` +- Fixed issue where enum values were interpreted as integers and range checked. (bug 62358540) +- Fixed issue where ints and floats with trailing whitespace would not be parsed. (bug 62902869) +- Fixed issue where `--custom-package` was not honored when writing Manifest.java. (bug 62826426) +- Add `<key-sets>` and its nested tags to the allowed set of XML tags in AndroidManifest.xml. + (bug 62839863) +- Fixed issue where Java classes referenced from fragments and menus were not added to + the set of Proguard keep rules. (bug 62216174) +- Fixed issue where escaped unicode characters would generate malformed UTF-8. (bug 62839202) +- Fixed issue where apostrophes or quotes used in XML attribute values were ignored. + (bug 62840406, 62840718) + ## Version 2.17 -### `aapt2 compile ...` -- Fixed an issue where symlinks would not be followed when compiling PNGs. (bug 62144459) +### `aapt2 ...` +- Fixed issue where symlinks would not be followed when compiling PNGs. (bug 62144459) - Fixed issue where overlays that declared `<add-resource>` did not compile. (bug 38355988) +- Fixed issue where `%n` in a string resource was interpreted as a format argument. (bug 37132275) +- Allow empty resources to compile, giving them a value of `""` or `@null`, depending on the + accepted formats. (bug 38425050) +- Resources declared via `<item>` with no format attribute were changed to accept all + resource types. (bug 62260121) +- Allow `<layout>` element under `<activity>` in AndroidManifest.xml. (bug 62189611) +- Fix issue where `--no-version-vector` did not apply to `pathInterpolator` and `objectAnimator`. + (bug 62211148) +- Fix issue where overlaid `<style>` would not be merged, and would replace the original resource + instead. This fix brings behavior in-line with AAPT. (bug 38355988) ## Version 2.16 ### `aapt2 link ...` diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index f3da780cacbd..6b8207647471 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -212,8 +212,7 @@ class StyleBuilder { } StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value) { - style_->entries.push_back( - Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)}); + style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)}); return *this; } @@ -224,7 +223,9 @@ class StyleBuilder { return *this; } - std::unique_ptr<Style> Build() { return std::move(style_); } + std::unique_ptr<Style> Build() { + return std::move(style_); + } private: DISALLOW_COPY_AND_ASSIGN(StyleBuilder); diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 8efd56a7514b..d7b46caf8c94 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -34,15 +34,6 @@ #include "io/File.h" #include "process/IResourceTableConsumer.h" -// -// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to -// fail to compile. -// -#define AAPT_ASSERT_TRUE(v) ASSERT_TRUE(bool(v)) -#define AAPT_ASSERT_FALSE(v) ASSERT_FALSE(bool(v)) -#define AAPT_EXPECT_TRUE(v) EXPECT_TRUE(bool(v)) -#define AAPT_EXPECT_FALSE(v) EXPECT_FALSE(bool(v)) - namespace aapt { namespace test { diff --git a/tools/aapt2/text/Unicode.cpp b/tools/aapt2/text/Unicode.cpp new file mode 100644 index 000000000000..75eeb46c7f5e --- /dev/null +++ b/tools/aapt2/text/Unicode.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "text/Unicode.h" + +#include <algorithm> +#include <array> + +#include "text/Utf8Iterator.h" + +using ::android::StringPiece; + +namespace aapt { +namespace text { + +namespace { + +struct CharacterProperties { + enum : uint32_t { + kXidStart = 1 << 0, + kXidContinue = 1 << 1, + }; + + char32_t first_char; + char32_t last_char; + uint32_t properties; +}; + +// Incude the generated data table. +#include "text/Unicode_data.cpp" + +bool CompareCharacterProperties(const CharacterProperties& a, char32_t codepoint) { + return a.last_char < codepoint; +} + +uint32_t FindCharacterProperties(char32_t codepoint) { + const auto iter_end = sCharacterProperties.end(); + const auto iter = std::lower_bound(sCharacterProperties.begin(), iter_end, codepoint, + CompareCharacterProperties); + if (iter != iter_end && codepoint >= iter->first_char) { + return iter->properties; + } + return 0u; +} + +} // namespace + +bool IsXidStart(char32_t codepoint) { + return FindCharacterProperties(codepoint) & CharacterProperties::kXidStart; +} + +bool IsXidContinue(char32_t codepoint) { + return FindCharacterProperties(codepoint) & CharacterProperties::kXidContinue; +} + +// Hardcode the White_Space characters since they are few and the external/icu project doesn't +// list them as data files to parse. +// Sourced from http://www.unicode.org/Public/UCD/latest/ucd/PropList.txt +bool IsWhitespace(char32_t codepoint) { + return (codepoint >= 0x0009 && codepoint <= 0x000d) || (codepoint == 0x0020) || + (codepoint == 0x0085) || (codepoint == 0x00a0) || (codepoint == 0x1680) || + (codepoint >= 0x2000 && codepoint <= 0x200a) || (codepoint == 0x2028) || + (codepoint == 0x2029) || (codepoint == 0x202f) || (codepoint == 0x205f) || + (codepoint == 0x3000); +} + +bool IsJavaIdentifier(const StringPiece& str) { + Utf8Iterator iter(str); + + // Check the first character. + if (!iter.HasNext()) { + return false; + } + + if (!IsXidStart(iter.Next())) { + return false; + } + + while (iter.HasNext()) { + const char32_t codepoint = iter.Next(); + if (!IsXidContinue(codepoint) && codepoint != U'$') { + return false; + } + } + return true; +} + +bool IsValidResourceEntryName(const StringPiece& str) { + Utf8Iterator iter(str); + + // Check the first character. + if (!iter.HasNext()) { + return false; + } + + // Resources are allowed to start with '_' + const char32_t first_codepoint = iter.Next(); + if (!IsXidStart(first_codepoint) && first_codepoint != U'_') { + return false; + } + + while (iter.HasNext()) { + const char32_t codepoint = iter.Next(); + if (!IsXidContinue(codepoint) && codepoint != U'.' && codepoint != U'-') { + return false; + } + } + return true; +} + +} // namespace text +} // namespace aapt diff --git a/tools/aapt2/text/Unicode.h b/tools/aapt2/text/Unicode.h new file mode 100644 index 000000000000..546714e9a290 --- /dev/null +++ b/tools/aapt2/text/Unicode.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_TEXT_UNICODE_H +#define AAPT_TEXT_UNICODE_H + +#include "androidfw/StringPiece.h" + +namespace aapt { +namespace text { + +// Returns true if the Unicode codepoint has the XID_Start property, meaning it can be used as the +// first character of a programming language identifier. +// http://unicode.org/reports/tr31/#Default_Identifier_Syntax +// +// XID_Start is a Unicode Derived Core Property. It is a variation of the ID_Start +// Derived Core Property, accounting for a few characters that, when normalized, yield valid +// characters in the ID_Start set. +bool IsXidStart(char32_t codepoint); + +// Returns true if the Unicode codepoint has the XID_Continue property, meaning it can be used in +// any position of a programming language identifier, except the first. +// http://unicode.org/reports/tr31/#Default_Identifier_Syntax +// +// XID_Continue is a Unicode Derived Core Property. It is a variation of the ID_Continue +// Derived Core Property, accounting for a few characters that, when normalized, yield valid +// characters in the ID_Continue set. +bool IsXidContinue(char32_t codepoint); + +// Returns true if the Unicode codepoint has the White_Space property. +// http://unicode.org/reports/tr44/#White_Space +bool IsWhitespace(char32_t codepoint); + +// Returns true if the UTF8 string can be used as a Java identifier. +// NOTE: This does not check against the set of reserved Java keywords. +bool IsJavaIdentifier(const android::StringPiece& str); + +// Returns true if the UTF8 string can be used as the entry name of a resource name. +// This is the `entry` part of package:type/entry. +bool IsValidResourceEntryName(const android::StringPiece& str); + +} // namespace text +} // namespace aapt + +#endif // AAPT_TEXT_UNICODE_H diff --git a/tools/aapt2/text/Unicode_data.cpp b/tools/aapt2/text/Unicode_data.cpp new file mode 100644 index 000000000000..96dc57b84031 --- /dev/null +++ b/tools/aapt2/text/Unicode_data.cpp @@ -0,0 +1,629 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const static std::array<CharacterProperties, 611> sCharacterProperties = {{ + {0x0030, 0x0039, CharacterProperties::kXidContinue}, + {0x0041, 0x005a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x005f, 0x005f, CharacterProperties::kXidContinue}, + {0x0061, 0x007a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00aa, 0x00aa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00b5, 0x00b5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00b7, 0x00b7, CharacterProperties::kXidContinue}, + {0x00ba, 0x00ba, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00c0, 0x00d6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00d8, 0x00f6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00f8, 0x02c1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x02c6, 0x02d1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x02e0, 0x02e4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x02ec, 0x02ec, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x02ee, 0x02ee, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0300, 0x036f, CharacterProperties::kXidContinue}, + {0x0370, 0x0374, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0376, 0x0377, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x037b, 0x037d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x037f, 0x037f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0386, 0x0386, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0387, 0x0387, CharacterProperties::kXidContinue}, + {0x0388, 0x038a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x038c, 0x038c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x038e, 0x03a1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x03a3, 0x03f5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x03f7, 0x0481, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0483, 0x0487, CharacterProperties::kXidContinue}, + {0x048a, 0x052f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0531, 0x0556, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0559, 0x0559, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0561, 0x0587, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0591, 0x05bd, CharacterProperties::kXidContinue}, + {0x05bf, 0x05bf, CharacterProperties::kXidContinue}, + {0x05c1, 0x05c2, CharacterProperties::kXidContinue}, + {0x05c4, 0x05c5, CharacterProperties::kXidContinue}, + {0x05c7, 0x05c7, CharacterProperties::kXidContinue}, + {0x05d0, 0x05ea, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x05f0, 0x05f2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0610, 0x061a, CharacterProperties::kXidContinue}, + {0x0620, 0x064a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x064b, 0x0669, CharacterProperties::kXidContinue}, + {0x066e, 0x066f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0670, 0x0670, CharacterProperties::kXidContinue}, + {0x0671, 0x06d3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x06d5, 0x06d5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x06d6, 0x06dc, CharacterProperties::kXidContinue}, + {0x06df, 0x06e4, CharacterProperties::kXidContinue}, + {0x06e5, 0x06e6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x06e7, 0x06e8, CharacterProperties::kXidContinue}, + {0x06ea, 0x06ed, CharacterProperties::kXidContinue}, + {0x06ee, 0x06ef, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x06f0, 0x06f9, CharacterProperties::kXidContinue}, + {0x06fa, 0x06fc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x06ff, 0x06ff, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0710, 0x0710, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0711, 0x0711, CharacterProperties::kXidContinue}, + {0x0712, 0x072f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0730, 0x074a, CharacterProperties::kXidContinue}, + {0x074d, 0x07a5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x07a6, 0x07b0, CharacterProperties::kXidContinue}, + {0x07b1, 0x07b1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x07c0, 0x07c9, CharacterProperties::kXidContinue}, + {0x07ca, 0x07ea, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x07eb, 0x07f3, CharacterProperties::kXidContinue}, + {0x07f4, 0x07f5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x07fa, 0x07fa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0800, 0x0815, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0816, 0x0819, CharacterProperties::kXidContinue}, + {0x081a, 0x081a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x081b, 0x0823, CharacterProperties::kXidContinue}, + {0x0824, 0x0824, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0825, 0x0827, CharacterProperties::kXidContinue}, + {0x0828, 0x0828, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0829, 0x082d, CharacterProperties::kXidContinue}, + {0x0840, 0x0858, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0859, 0x085b, CharacterProperties::kXidContinue}, + {0x08a0, 0x08b4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x08b6, 0x08bd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x08d4, 0x08e1, CharacterProperties::kXidContinue}, + {0x08e3, 0x0903, CharacterProperties::kXidContinue}, + {0x0904, 0x0939, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x093a, 0x093c, CharacterProperties::kXidContinue}, + {0x093d, 0x093d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x093e, 0x094f, CharacterProperties::kXidContinue}, + {0x0950, 0x0950, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0951, 0x0957, CharacterProperties::kXidContinue}, + {0x0958, 0x0961, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0962, 0x0963, CharacterProperties::kXidContinue}, + {0x0966, 0x096f, CharacterProperties::kXidContinue}, + {0x0971, 0x0980, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0981, 0x0983, CharacterProperties::kXidContinue}, + {0x0985, 0x098c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x098f, 0x0990, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0993, 0x09a8, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09aa, 0x09b0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09b2, 0x09b2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09b6, 0x09b9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09bc, 0x09bc, CharacterProperties::kXidContinue}, + {0x09bd, 0x09bd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09be, 0x09c4, CharacterProperties::kXidContinue}, + {0x09c7, 0x09c8, CharacterProperties::kXidContinue}, + {0x09cb, 0x09cd, CharacterProperties::kXidContinue}, + {0x09ce, 0x09ce, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09d7, 0x09d7, CharacterProperties::kXidContinue}, + {0x09dc, 0x09dd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09df, 0x09e1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09e2, 0x09e3, CharacterProperties::kXidContinue}, + {0x09e6, 0x09ef, CharacterProperties::kXidContinue}, + {0x09f0, 0x09f1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a01, 0x0a03, CharacterProperties::kXidContinue}, + {0x0a05, 0x0a0a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a0f, 0x0a10, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a13, 0x0a28, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a2a, 0x0a30, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a32, 0x0a33, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a35, 0x0a36, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a38, 0x0a39, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a3c, 0x0a3c, CharacterProperties::kXidContinue}, + {0x0a3e, 0x0a42, CharacterProperties::kXidContinue}, + {0x0a47, 0x0a48, CharacterProperties::kXidContinue}, + {0x0a4b, 0x0a4d, CharacterProperties::kXidContinue}, + {0x0a51, 0x0a51, CharacterProperties::kXidContinue}, + {0x0a59, 0x0a5c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a5e, 0x0a5e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a66, 0x0a71, CharacterProperties::kXidContinue}, + {0x0a72, 0x0a74, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a75, 0x0a75, CharacterProperties::kXidContinue}, + {0x0a81, 0x0a83, CharacterProperties::kXidContinue}, + {0x0a85, 0x0a8d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a8f, 0x0a91, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a93, 0x0aa8, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0aaa, 0x0ab0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ab2, 0x0ab3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ab5, 0x0ab9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0abc, 0x0abc, CharacterProperties::kXidContinue}, + {0x0abd, 0x0abd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0abe, 0x0ac5, CharacterProperties::kXidContinue}, + {0x0ac7, 0x0ac9, CharacterProperties::kXidContinue}, + {0x0acb, 0x0acd, CharacterProperties::kXidContinue}, + {0x0ad0, 0x0ad0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ae0, 0x0ae1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ae2, 0x0ae3, CharacterProperties::kXidContinue}, + {0x0ae6, 0x0aef, CharacterProperties::kXidContinue}, + {0x0af9, 0x0af9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b01, 0x0b03, CharacterProperties::kXidContinue}, + {0x0b05, 0x0b0c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b0f, 0x0b10, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b13, 0x0b28, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b2a, 0x0b30, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b32, 0x0b33, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b35, 0x0b39, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b3c, 0x0b3c, CharacterProperties::kXidContinue}, + {0x0b3d, 0x0b3d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b3e, 0x0b44, CharacterProperties::kXidContinue}, + {0x0b47, 0x0b48, CharacterProperties::kXidContinue}, + {0x0b4b, 0x0b4d, CharacterProperties::kXidContinue}, + {0x0b56, 0x0b57, CharacterProperties::kXidContinue}, + {0x0b5c, 0x0b5d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b5f, 0x0b61, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b62, 0x0b63, CharacterProperties::kXidContinue}, + {0x0b66, 0x0b6f, CharacterProperties::kXidContinue}, + {0x0b71, 0x0b71, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b82, 0x0b82, CharacterProperties::kXidContinue}, + {0x0b83, 0x0b83, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b85, 0x0b8a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b8e, 0x0b90, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b92, 0x0b95, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b99, 0x0b9a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b9c, 0x0b9c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b9e, 0x0b9f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ba3, 0x0ba4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ba8, 0x0baa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0bae, 0x0bb9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0bbe, 0x0bc2, CharacterProperties::kXidContinue}, + {0x0bc6, 0x0bc8, CharacterProperties::kXidContinue}, + {0x0bca, 0x0bcd, CharacterProperties::kXidContinue}, + {0x0bd0, 0x0bd0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0bd7, 0x0bd7, CharacterProperties::kXidContinue}, + {0x0be6, 0x0bef, CharacterProperties::kXidContinue}, + {0x0c00, 0x0c03, CharacterProperties::kXidContinue}, + {0x0c05, 0x0c0c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c0e, 0x0c10, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c12, 0x0c28, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c2a, 0x0c39, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c3d, 0x0c3d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c3e, 0x0c44, CharacterProperties::kXidContinue}, + {0x0c46, 0x0c48, CharacterProperties::kXidContinue}, + {0x0c4a, 0x0c4d, CharacterProperties::kXidContinue}, + {0x0c55, 0x0c56, CharacterProperties::kXidContinue}, + {0x0c58, 0x0c5a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c60, 0x0c61, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c62, 0x0c63, CharacterProperties::kXidContinue}, + {0x0c66, 0x0c6f, CharacterProperties::kXidContinue}, + {0x0c80, 0x0c80, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c81, 0x0c83, CharacterProperties::kXidContinue}, + {0x0c85, 0x0c8c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c8e, 0x0c90, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c92, 0x0ca8, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0caa, 0x0cb3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0cb5, 0x0cb9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0cbc, 0x0cbc, CharacterProperties::kXidContinue}, + {0x0cbd, 0x0cbd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0cbe, 0x0cc4, CharacterProperties::kXidContinue}, + {0x0cc6, 0x0cc8, CharacterProperties::kXidContinue}, + {0x0cca, 0x0ccd, CharacterProperties::kXidContinue}, + {0x0cd5, 0x0cd6, CharacterProperties::kXidContinue}, + {0x0cde, 0x0cde, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ce0, 0x0ce1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ce2, 0x0ce3, CharacterProperties::kXidContinue}, + {0x0ce6, 0x0cef, CharacterProperties::kXidContinue}, + {0x0cf1, 0x0cf2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d01, 0x0d03, CharacterProperties::kXidContinue}, + {0x0d05, 0x0d0c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d0e, 0x0d10, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d12, 0x0d3a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d3d, 0x0d3d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d3e, 0x0d44, CharacterProperties::kXidContinue}, + {0x0d46, 0x0d48, CharacterProperties::kXidContinue}, + {0x0d4a, 0x0d4d, CharacterProperties::kXidContinue}, + {0x0d4e, 0x0d4e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d54, 0x0d56, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d57, 0x0d57, CharacterProperties::kXidContinue}, + {0x0d5f, 0x0d61, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d62, 0x0d63, CharacterProperties::kXidContinue}, + {0x0d66, 0x0d6f, CharacterProperties::kXidContinue}, + {0x0d7a, 0x0d7f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d82, 0x0d83, CharacterProperties::kXidContinue}, + {0x0d85, 0x0d96, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d9a, 0x0db1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0db3, 0x0dbb, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0dbd, 0x0dbd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0dc0, 0x0dc6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0dca, 0x0dca, CharacterProperties::kXidContinue}, + {0x0dcf, 0x0dd4, CharacterProperties::kXidContinue}, + {0x0dd6, 0x0dd6, CharacterProperties::kXidContinue}, + {0x0dd8, 0x0ddf, CharacterProperties::kXidContinue}, + {0x0de6, 0x0def, CharacterProperties::kXidContinue}, + {0x0df2, 0x0df3, CharacterProperties::kXidContinue}, + {0x0e01, 0x0e30, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e31, 0x0e31, CharacterProperties::kXidContinue}, + {0x0e32, 0x0e32, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e33, 0x0e3a, CharacterProperties::kXidContinue}, + {0x0e40, 0x0e46, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e47, 0x0e4e, CharacterProperties::kXidContinue}, + {0x0e50, 0x0e59, CharacterProperties::kXidContinue}, + {0x0e81, 0x0e82, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e84, 0x0e84, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e87, 0x0e88, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e8a, 0x0e8a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e8d, 0x0e8d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e94, 0x0e97, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e99, 0x0e9f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ea1, 0x0ea3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ea5, 0x0ea5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ea7, 0x0ea7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0eaa, 0x0eab, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ead, 0x0eb0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0eb1, 0x0eb1, CharacterProperties::kXidContinue}, + {0x0eb2, 0x0eb2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0eb3, 0x0eb9, CharacterProperties::kXidContinue}, + {0x0ebb, 0x0ebc, CharacterProperties::kXidContinue}, + {0x0ebd, 0x0ebd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ec0, 0x0ec4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ec6, 0x0ec6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ec8, 0x0ecd, CharacterProperties::kXidContinue}, + {0x0ed0, 0x0ed9, CharacterProperties::kXidContinue}, + {0x0edc, 0x0edf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0f00, 0x0f00, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0f18, 0x0f19, CharacterProperties::kXidContinue}, + {0x0f20, 0x0f29, CharacterProperties::kXidContinue}, + {0x0f35, 0x0f35, CharacterProperties::kXidContinue}, + {0x0f37, 0x0f37, CharacterProperties::kXidContinue}, + {0x0f39, 0x0f39, CharacterProperties::kXidContinue}, + {0x0f3e, 0x0f3f, CharacterProperties::kXidContinue}, + {0x0f40, 0x0f47, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0f49, 0x0f6c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0f71, 0x0f84, CharacterProperties::kXidContinue}, + {0x0f86, 0x0f87, CharacterProperties::kXidContinue}, + {0x0f88, 0x0f8c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0f8d, 0x0f97, CharacterProperties::kXidContinue}, + {0x0f99, 0x0fbc, CharacterProperties::kXidContinue}, + {0x0fc6, 0x0fc6, CharacterProperties::kXidContinue}, + {0x1000, 0x102a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x102b, 0x103e, CharacterProperties::kXidContinue}, + {0x103f, 0x103f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1040, 0x1049, CharacterProperties::kXidContinue}, + {0x1050, 0x1055, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1056, 0x1059, CharacterProperties::kXidContinue}, + {0x105a, 0x105d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x105e, 0x1060, CharacterProperties::kXidContinue}, + {0x1061, 0x1061, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1062, 0x1064, CharacterProperties::kXidContinue}, + {0x1065, 0x1066, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1067, 0x106d, CharacterProperties::kXidContinue}, + {0x106e, 0x1070, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1071, 0x1074, CharacterProperties::kXidContinue}, + {0x1075, 0x1081, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1082, 0x108d, CharacterProperties::kXidContinue}, + {0x108e, 0x108e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x108f, 0x109d, CharacterProperties::kXidContinue}, + {0x10a0, 0x10c5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x10c7, 0x10c7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x10cd, 0x10cd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x10d0, 0x10fa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x10fc, 0x1248, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x124a, 0x124d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1250, 0x1256, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1258, 0x1258, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x125a, 0x125d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1260, 0x1288, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x128a, 0x128d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1290, 0x12b0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12b2, 0x12b5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12b8, 0x12be, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12c0, 0x12c0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12c2, 0x12c5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12c8, 0x12d6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12d8, 0x1310, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1312, 0x1315, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1318, 0x135a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x135d, 0x135f, CharacterProperties::kXidContinue}, + {0x1369, 0x1371, CharacterProperties::kXidContinue}, + {0x1380, 0x138f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x13a0, 0x13f5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x13f8, 0x13fd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1401, 0x166c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x166f, 0x167f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1681, 0x169a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x16a0, 0x16ea, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x16ee, 0x16f8, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1700, 0x170c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x170e, 0x1711, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1712, 0x1714, CharacterProperties::kXidContinue}, + {0x1720, 0x1731, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1732, 0x1734, CharacterProperties::kXidContinue}, + {0x1740, 0x1751, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1752, 0x1753, CharacterProperties::kXidContinue}, + {0x1760, 0x176c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x176e, 0x1770, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1772, 0x1773, CharacterProperties::kXidContinue}, + {0x1780, 0x17b3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x17b4, 0x17d3, CharacterProperties::kXidContinue}, + {0x17d7, 0x17d7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x17dc, 0x17dc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x17dd, 0x17dd, CharacterProperties::kXidContinue}, + {0x17e0, 0x17e9, CharacterProperties::kXidContinue}, + {0x180b, 0x180d, CharacterProperties::kXidContinue}, + {0x1810, 0x1819, CharacterProperties::kXidContinue}, + {0x1820, 0x1877, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1880, 0x18a8, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x18a9, 0x18a9, CharacterProperties::kXidContinue}, + {0x18aa, 0x18aa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x18b0, 0x18f5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1900, 0x191e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1920, 0x192b, CharacterProperties::kXidContinue}, + {0x1930, 0x193b, CharacterProperties::kXidContinue}, + {0x1946, 0x194f, CharacterProperties::kXidContinue}, + {0x1950, 0x196d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1970, 0x1974, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1980, 0x19ab, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x19b0, 0x19c9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x19d0, 0x19da, CharacterProperties::kXidContinue}, + {0x1a00, 0x1a16, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1a17, 0x1a1b, CharacterProperties::kXidContinue}, + {0x1a20, 0x1a54, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1a55, 0x1a5e, CharacterProperties::kXidContinue}, + {0x1a60, 0x1a7c, CharacterProperties::kXidContinue}, + {0x1a7f, 0x1a89, CharacterProperties::kXidContinue}, + {0x1a90, 0x1a99, CharacterProperties::kXidContinue}, + {0x1aa7, 0x1aa7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1ab0, 0x1abd, CharacterProperties::kXidContinue}, + {0x1b00, 0x1b04, CharacterProperties::kXidContinue}, + {0x1b05, 0x1b33, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1b34, 0x1b44, CharacterProperties::kXidContinue}, + {0x1b45, 0x1b4b, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1b50, 0x1b59, CharacterProperties::kXidContinue}, + {0x1b6b, 0x1b73, CharacterProperties::kXidContinue}, + {0x1b80, 0x1b82, CharacterProperties::kXidContinue}, + {0x1b83, 0x1ba0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1ba1, 0x1bad, CharacterProperties::kXidContinue}, + {0x1bae, 0x1baf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1bb0, 0x1bb9, CharacterProperties::kXidContinue}, + {0x1bba, 0x1be5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1be6, 0x1bf3, CharacterProperties::kXidContinue}, + {0x1c00, 0x1c23, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1c24, 0x1c37, CharacterProperties::kXidContinue}, + {0x1c40, 0x1c49, CharacterProperties::kXidContinue}, + {0x1c4d, 0x1c4f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1c50, 0x1c59, CharacterProperties::kXidContinue}, + {0x1c5a, 0x1c7d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1c80, 0x1c88, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1cd0, 0x1cd2, CharacterProperties::kXidContinue}, + {0x1cd4, 0x1ce8, CharacterProperties::kXidContinue}, + {0x1ce9, 0x1cec, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1ced, 0x1ced, CharacterProperties::kXidContinue}, + {0x1cee, 0x1cf1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1cf2, 0x1cf4, CharacterProperties::kXidContinue}, + {0x1cf5, 0x1cf6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1cf8, 0x1cf9, CharacterProperties::kXidContinue}, + {0x1d00, 0x1dbf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1dc0, 0x1df5, CharacterProperties::kXidContinue}, + {0x1dfb, 0x1dff, CharacterProperties::kXidContinue}, + {0x1e00, 0x1f15, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f18, 0x1f1d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f20, 0x1f45, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f48, 0x1f4d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f50, 0x1f57, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f59, 0x1f59, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f5b, 0x1f5b, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f5d, 0x1f5d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f5f, 0x1f7d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f80, 0x1fb4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fb6, 0x1fbc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fbe, 0x1fbe, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fc2, 0x1fc4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fc6, 0x1fcc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fd0, 0x1fd3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fd6, 0x1fdb, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fe0, 0x1fec, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1ff2, 0x1ff4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1ff6, 0x1ffc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x203f, 0x2040, CharacterProperties::kXidContinue}, + {0x2054, 0x2054, CharacterProperties::kXidContinue}, + {0x2071, 0x2071, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x207f, 0x207f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2090, 0x209c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x20d0, 0x20dc, CharacterProperties::kXidContinue}, + {0x20e1, 0x20e1, CharacterProperties::kXidContinue}, + {0x20e5, 0x20f0, CharacterProperties::kXidContinue}, + {0x2102, 0x2102, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2107, 0x2107, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x210a, 0x2113, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2115, 0x2115, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2118, 0x211d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2124, 0x2124, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2126, 0x2126, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2128, 0x2128, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x212a, 0x2139, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x213c, 0x213f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2145, 0x2149, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x214e, 0x214e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2160, 0x2188, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2c00, 0x2c2e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2c30, 0x2c5e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2c60, 0x2ce4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2ceb, 0x2cee, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2cef, 0x2cf1, CharacterProperties::kXidContinue}, + {0x2cf2, 0x2cf3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d00, 0x2d25, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d27, 0x2d27, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d2d, 0x2d2d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d30, 0x2d67, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d6f, 0x2d6f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d7f, 0x2d7f, CharacterProperties::kXidContinue}, + {0x2d80, 0x2d96, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2da0, 0x2da6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2da8, 0x2dae, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2db0, 0x2db6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2db8, 0x2dbe, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2dc0, 0x2dc6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2dc8, 0x2dce, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2dd0, 0x2dd6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2dd8, 0x2dde, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2de0, 0x2dff, CharacterProperties::kXidContinue}, + {0x3005, 0x3007, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3021, 0x3029, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x302a, 0x302f, CharacterProperties::kXidContinue}, + {0x3031, 0x3035, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3038, 0x303c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3041, 0x3096, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3099, 0x309a, CharacterProperties::kXidContinue}, + {0x309d, 0x309f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x30a1, 0x30fa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x30fc, 0x30ff, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3105, 0x312d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3131, 0x318e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x31a0, 0x31ba, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x31f0, 0x31ff, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3400, 0x4db5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x4e00, 0x9fd5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa000, 0xa48c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa4d0, 0xa4fd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa500, 0xa60c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa610, 0xa61f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa620, 0xa629, CharacterProperties::kXidContinue}, + {0xa62a, 0xa62b, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa640, 0xa66e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa66f, 0xa66f, CharacterProperties::kXidContinue}, + {0xa674, 0xa67d, CharacterProperties::kXidContinue}, + {0xa67f, 0xa69d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa69e, 0xa69f, CharacterProperties::kXidContinue}, + {0xa6a0, 0xa6ef, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa6f0, 0xa6f1, CharacterProperties::kXidContinue}, + {0xa717, 0xa71f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa722, 0xa788, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa78b, 0xa7ae, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa7b0, 0xa7b7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa7f7, 0xa801, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa802, 0xa802, CharacterProperties::kXidContinue}, + {0xa803, 0xa805, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa806, 0xa806, CharacterProperties::kXidContinue}, + {0xa807, 0xa80a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa80b, 0xa80b, CharacterProperties::kXidContinue}, + {0xa80c, 0xa822, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa823, 0xa827, CharacterProperties::kXidContinue}, + {0xa840, 0xa873, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa880, 0xa881, CharacterProperties::kXidContinue}, + {0xa882, 0xa8b3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa8b4, 0xa8c5, CharacterProperties::kXidContinue}, + {0xa8d0, 0xa8d9, CharacterProperties::kXidContinue}, + {0xa8e0, 0xa8f1, CharacterProperties::kXidContinue}, + {0xa8f2, 0xa8f7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa8fb, 0xa8fb, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa8fd, 0xa8fd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa900, 0xa909, CharacterProperties::kXidContinue}, + {0xa90a, 0xa925, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa926, 0xa92d, CharacterProperties::kXidContinue}, + {0xa930, 0xa946, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa947, 0xa953, CharacterProperties::kXidContinue}, + {0xa960, 0xa97c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa980, 0xa983, CharacterProperties::kXidContinue}, + {0xa984, 0xa9b2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa9b3, 0xa9c0, CharacterProperties::kXidContinue}, + {0xa9cf, 0xa9cf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa9d0, 0xa9d9, CharacterProperties::kXidContinue}, + {0xa9e0, 0xa9e4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa9e5, 0xa9e5, CharacterProperties::kXidContinue}, + {0xa9e6, 0xa9ef, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa9f0, 0xa9f9, CharacterProperties::kXidContinue}, + {0xa9fa, 0xa9fe, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa00, 0xaa28, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa29, 0xaa36, CharacterProperties::kXidContinue}, + {0xaa40, 0xaa42, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa43, 0xaa43, CharacterProperties::kXidContinue}, + {0xaa44, 0xaa4b, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa4c, 0xaa4d, CharacterProperties::kXidContinue}, + {0xaa50, 0xaa59, CharacterProperties::kXidContinue}, + {0xaa60, 0xaa76, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa7a, 0xaa7a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa7b, 0xaa7d, CharacterProperties::kXidContinue}, + {0xaa7e, 0xaaaf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaab0, 0xaab0, CharacterProperties::kXidContinue}, + {0xaab1, 0xaab1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaab2, 0xaab4, CharacterProperties::kXidContinue}, + {0xaab5, 0xaab6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaab7, 0xaab8, CharacterProperties::kXidContinue}, + {0xaab9, 0xaabd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaabe, 0xaabf, CharacterProperties::kXidContinue}, + {0xaac0, 0xaac0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaac1, 0xaac1, CharacterProperties::kXidContinue}, + {0xaac2, 0xaac2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaadb, 0xaadd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaae0, 0xaaea, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaaeb, 0xaaef, CharacterProperties::kXidContinue}, + {0xaaf2, 0xaaf4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaaf5, 0xaaf6, CharacterProperties::kXidContinue}, + {0xab01, 0xab06, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab09, 0xab0e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab11, 0xab16, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab20, 0xab26, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab28, 0xab2e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab30, 0xab5a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab5c, 0xab65, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab70, 0xabe2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xabe3, 0xabea, CharacterProperties::kXidContinue}, + {0xabec, 0xabed, CharacterProperties::kXidContinue}, + {0xabf0, 0xabf9, CharacterProperties::kXidContinue}, + {0xac00, 0xd7a3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xd7b0, 0xd7c6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xd7cb, 0xd7fb, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xf900, 0xfa6d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfa70, 0xfad9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb00, 0xfb06, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb13, 0xfb17, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb1d, 0xfb1d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb1e, 0xfb1e, CharacterProperties::kXidContinue}, + {0xfb1f, 0xfb28, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb2a, 0xfb36, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb38, 0xfb3c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb3e, 0xfb3e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb40, 0xfb41, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb43, 0xfb44, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb46, 0xfbb1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfbd3, 0xfc5d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfc64, 0xfd3d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfd50, 0xfd8f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfd92, 0xfdc7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfdf0, 0xfdf9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe00, 0xfe0f, CharacterProperties::kXidContinue}, + {0xfe20, 0xfe2f, CharacterProperties::kXidContinue}, + {0xfe33, 0xfe34, CharacterProperties::kXidContinue}, + {0xfe4d, 0xfe4f, CharacterProperties::kXidContinue}, + {0xfe71, 0xfe71, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe73, 0xfe73, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe77, 0xfe77, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe79, 0xfe79, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe7b, 0xfe7b, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe7d, 0xfe7d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe7f, 0xfefc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xff10, 0xff19, CharacterProperties::kXidContinue}, + {0xff21, 0xff3a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xff3f, 0xff3f, CharacterProperties::kXidContinue}, + {0xff41, 0xff5a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xff66, 0xff9d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xff9e, 0xff9f, CharacterProperties::kXidContinue}, + {0xffa0, 0xffbe, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xffc2, 0xffc7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xffca, 0xffcf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xffd2, 0xffd7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xffda, 0xffdc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, +}}; diff --git a/tools/aapt2/text/Unicode_test.cpp b/tools/aapt2/text/Unicode_test.cpp new file mode 100644 index 000000000000..d47fb282bc0a --- /dev/null +++ b/tools/aapt2/text/Unicode_test.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "text/Unicode.h" + +#include "test/Test.h" + +using ::testing::Each; +using ::testing::Eq; +using ::testing::ResultOf; + +namespace aapt { +namespace text { + +TEST(UnicodeTest, IsXidStart) { + std::u32string valid_input = U"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZˮø"; + EXPECT_THAT(valid_input, Each(ResultOf(IsXidStart, Eq(true)))); + + std::u32string invalid_input = U"$;\'/<>+=-.{}[]()\\|?@#%^&*!~`\",1234567890_"; + EXPECT_THAT(invalid_input, Each(ResultOf(IsXidStart, Eq(false)))); +} + +TEST(UnicodeTest, IsXidContinue) { + std::u32string valid_input = U"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_ˮø"; + EXPECT_THAT(valid_input, Each(ResultOf(IsXidContinue, Eq(true)))); + + std::u32string invalid_input = U"$;\'/<>+=-.{}[]()\\|?@#%^&*!~`\","; + EXPECT_THAT(invalid_input, Each(ResultOf(IsXidContinue, Eq(false)))); +} + +TEST(UnicodeTest, IsJavaIdentifier) { + EXPECT_TRUE(IsJavaIdentifier("FøøBar_12")); + EXPECT_TRUE(IsJavaIdentifier("Føø$Bar")); + + EXPECT_FALSE(IsJavaIdentifier("12FøøBar")); + EXPECT_FALSE(IsJavaIdentifier("_FøøBar")); + EXPECT_FALSE(IsJavaIdentifier("$Føø$Bar")); +} + +TEST(UnicodeTest, IsValidResourceEntryName) { + EXPECT_TRUE(IsJavaIdentifier("FøøBar")); + EXPECT_TRUE(IsValidResourceEntryName("FøøBar_12")); + EXPECT_TRUE(IsValidResourceEntryName("Føø.Bar")); + EXPECT_TRUE(IsValidResourceEntryName("Føø-Bar")); + EXPECT_TRUE(IsValidResourceEntryName("_FøøBar")); + + EXPECT_FALSE(IsValidResourceEntryName("12FøøBar")); + EXPECT_FALSE(IsValidResourceEntryName("Føø$Bar")); + EXPECT_FALSE(IsValidResourceEntryName("Føø/Bar")); + EXPECT_FALSE(IsValidResourceEntryName("Føø:Bar")); + EXPECT_FALSE(IsValidResourceEntryName("Føø;Bar")); +} + +} // namespace text +} // namespace aapt diff --git a/tools/aapt2/text/Utf8Iterator.cpp b/tools/aapt2/text/Utf8Iterator.cpp new file mode 100644 index 000000000000..20b9073b9a26 --- /dev/null +++ b/tools/aapt2/text/Utf8Iterator.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "text/Utf8Iterator.h" + +#include "android-base/logging.h" +#include "utils/Unicode.h" + +using ::android::StringPiece; + +namespace aapt { +namespace text { + +Utf8Iterator::Utf8Iterator(const StringPiece& str) + : str_(str), current_pos_(0), next_pos_(0), current_codepoint_(0) { + DoNext(); +} + +void Utf8Iterator::DoNext() { + current_pos_ = next_pos_; + int32_t result = utf32_from_utf8_at(str_.data(), str_.size(), current_pos_, &next_pos_); + if (result == -1) { + current_codepoint_ = 0u; + } else { + current_codepoint_ = static_cast<char32_t>(result); + } +} + +bool Utf8Iterator::HasNext() const { + return current_codepoint_ != 0; +} + +size_t Utf8Iterator::Position() const { + return current_pos_; +} + +void Utf8Iterator::Skip(int amount) { + while (amount > 0 && HasNext()) { + Next(); + --amount; + } +} + +char32_t Utf8Iterator::Next() { + CHECK(HasNext()) << "Next() called after iterator exhausted"; + char32_t result = current_codepoint_; + DoNext(); + return result; +} + +} // namespace text +} // namespace aapt diff --git a/tools/aapt2/text/Utf8Iterator.h b/tools/aapt2/text/Utf8Iterator.h new file mode 100644 index 000000000000..9318401876d1 --- /dev/null +++ b/tools/aapt2/text/Utf8Iterator.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_TEXT_UTF8ITERATOR_H +#define AAPT_TEXT_UTF8ITERATOR_H + +#include "android-base/macros.h" +#include "androidfw/StringPiece.h" + +namespace aapt { +namespace text { + +class Utf8Iterator { + public: + explicit Utf8Iterator(const android::StringPiece& str); + + bool HasNext() const; + + // Returns the current position of the iterator in bytes of the source UTF8 string. + // This position is the start of the codepoint returned by the next call to Next(). + size_t Position() const; + + void Skip(int amount); + + char32_t Next(); + + private: + DISALLOW_COPY_AND_ASSIGN(Utf8Iterator); + + void DoNext(); + + android::StringPiece str_; + size_t current_pos_; + size_t next_pos_; + char32_t current_codepoint_; +}; + +} // namespace text +} // namespace aapt + +#endif // AAPT_TEXT_UTF8ITERATOR_H diff --git a/tools/aapt2/text/Utf8Iterator_test.cpp b/tools/aapt2/text/Utf8Iterator_test.cpp new file mode 100644 index 000000000000..8c3e77446595 --- /dev/null +++ b/tools/aapt2/text/Utf8Iterator_test.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "text/Utf8Iterator.h" + +#include "test/Test.h" + +using ::android::StringPiece; +using ::testing::Eq; + +namespace aapt { +namespace text { + +TEST(Utf8IteratorTest, IteratesOverAscii) { + Utf8Iterator iter("hello"); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'h')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'e')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'l')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'l')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'o')); + + EXPECT_FALSE(iter.HasNext()); +} + +TEST(Utf8IteratorTest, IteratesOverUnicode) { + Utf8Iterator iter("Hi there 華勵蓮🍩"); + iter.Skip(9); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'華')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'勵')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'蓮')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'🍩')); + + EXPECT_FALSE(iter.HasNext()); +} + +TEST(Utf8IteratorTest, PositionPointsToTheCorrectPlace) { + const StringPiece expected("Mm🍩"); + Utf8Iterator iter(expected); + + // Before any character, the position should be 0. + EXPECT_THAT(iter.Position(), Eq(0u)); + + // The 'M' character, one byte. + ASSERT_TRUE(iter.HasNext()); + iter.Next(); + EXPECT_THAT(iter.Position(), Eq(1u)); + + // The 'm' character, one byte. + ASSERT_TRUE(iter.HasNext()); + iter.Next(); + EXPECT_THAT(iter.Position(), Eq(2u)); + + // The doughnut character, 4 bytes. + ASSERT_TRUE(iter.HasNext()); + iter.Next(); + EXPECT_THAT(iter.Position(), Eq(6u)); + + // There should be nothing left. + EXPECT_FALSE(iter.HasNext()); + EXPECT_THAT(iter.Position(), Eq(expected.size())); +} + +} // namespace text +} // namespace aapt diff --git a/tools/aapt2/tools/extract_unicode_properties.py b/tools/aapt2/tools/extract_unicode_properties.py new file mode 100644 index 000000000000..7577ec82aa86 --- /dev/null +++ b/tools/aapt2/tools/extract_unicode_properties.py @@ -0,0 +1,102 @@ +#!/bin/env python3 + +"""Extracts the XID_Start and XID_Continue Derived core properties from the ICU data files +and emits a std::array<> for binary searching. +""" + +import re +import sys + +CharacterPropertyEnumMap = { + 1: "CharacterProperties::kXidStart", + 2: "CharacterProperties::kXidContinue" +} + +class CharacterProperty: + def __init__(self, first_char, last_char, prop_type): + self.first_char = first_char + self.last_char = last_char + self.prop_type = prop_type + + def key(self): + return self.first_char + + def merge(self, other): + if self.last_char + 1 == other.first_char and self.prop_type == other.prop_type: + self.last_char = other.last_char + else: + raise KeyError() + + def __repr__(self): + types = [] + for enum_int, enum_str in CharacterPropertyEnumMap.items(): + if enum_int & self.prop_type: + types.append(enum_str) + return "{}0x{:04x}, 0x{:04x}, {}{}".format( + "{", self.first_char, self.last_char, ' | '.join(types), "}") + +def extract_unicode_properties(f, props, chars_out): + prog = re.compile(r"^(?P<first>\w{4})(..(?P<last>\w{4}))?\W+;\W+(?P<prop>\w+)") + for line in f: + result = prog.match(line) + if result: + prop_type_str = result.group('prop') + first_char_str = result.group('first') + last_char_str = result.group('last') + if prop_type_str in props: + start_char = int(first_char_str, 16) + last_char = (int(last_char_str, 16) if last_char_str else start_char) + 1 + prop_type = props[prop_type_str] + for char in range(start_char, last_char): + if char not in chars_out: + chars_out[char] = CharacterProperty(char, char, 0) + chars_out[char].prop_type |= prop_type + return chars_out + +def flatten_unicode_properties(chars): + result = [] + for char_prop in sorted(chars.values(), key=CharacterProperty.key): + if len(result) == 0: + result.append(char_prop) + else: + try: + result[len(result) - 1].merge(char_prop) + except KeyError: + result.append(char_prop) + return result + +license = """/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +""" + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("must specify path to icu DerivedCoreProperties file (e.g:" \ + "external/icu/icu4c/source/data/unidata/DerivedCoreProperties.txt)") + sys.exit(1) + + props = {"XID_Start": 1, "XID_Continue": 2} + char_props = {} + for file_path in sys.argv[1:]: + with open(file_path) as f: + extract_unicode_properties(f, props, char_props) + result = flatten_unicode_properties(char_props) + print("{}\nconst static std::array<CharacterProperties, {}> sCharacterProperties = {}" + .format(license, len(result), "{{")) + for prop in result: + print(" {},".format(prop)) + print("}};") + diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index f3116701056b..728d1f4207c4 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -38,20 +38,17 @@ namespace aapt { using namespace android; -using android::base::StringPrintf; +using ::android::base::StringPrintf; namespace { -/* - * Visitor that converts a reference's resource ID to a resource name, - * given a mapping from resource ID to resource name. - */ +// Visitor that converts a reference's resource ID to a resource name, given a mapping from +// resource ID to resource name. class ReferenceIdToNameVisitor : public ValueVisitor { public: using ValueVisitor::Visit; - explicit ReferenceIdToNameVisitor( - const std::map<ResourceId, ResourceName>* mapping) + explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) : mapping_(mapping) { CHECK(mapping_ != nullptr); } @@ -99,7 +96,7 @@ bool BinaryResourceParser::Parse() { if (parser.chunk()->type != android::RES_TABLE_TYPE) { context_->GetDiagnostics()->Error(DiagMessage(source_) << StringPrintf("unknown chunk of type 0x%02x", - (int)parser.chunk()->type)); + static_cast<int>(parser.chunk()->type))); return false; } @@ -115,7 +112,7 @@ bool BinaryResourceParser::Parse() { context_->GetDiagnostics()->Warn( DiagMessage(source_) << StringPrintf( "unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE", - (int)parser.chunk()->type)); + static_cast<int>(parser.chunk()->type))); } } return true; @@ -165,9 +162,8 @@ bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { default: context_->GetDiagnostics()->Warn( - DiagMessage(source_) - << "unexpected chunk type " - << (int)util::DeviceToHost16(parser.chunk()->type)); + DiagMessage(source_) << "unexpected chunk type " + << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); break; } } @@ -245,8 +241,7 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { return false; } } else { - context_->GetDiagnostics()->Warn(DiagMessage(source_) - << "unexpected string pool"); + context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool"); } break; @@ -270,9 +265,8 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { default: context_->GetDiagnostics()->Warn( - DiagMessage(source_) - << "unexpected chunk type " - << (int)util::DeviceToHost16(parser.chunk()->type)); + DiagMessage(source_) << "unexpected chunk type " + << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); break; } } diff --git a/tools/aapt2/util/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp index 12c0b3ee3214..a7776e33ae74 100644 --- a/tools/aapt2/util/BigBuffer_test.cpp +++ b/tools/aapt2/util/BigBuffer_test.cpp @@ -18,12 +18,14 @@ #include "test/Test.h" +using ::testing::NotNull; + namespace aapt { TEST(BigBufferTest, AllocateSingleBlock) { BigBuffer buffer(4); - EXPECT_NE(nullptr, buffer.NextBlock<char>(2)); + EXPECT_THAT(buffer.NextBlock<char>(2), NotNull()); EXPECT_EQ(2u, buffer.size()); } @@ -31,10 +33,10 @@ TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) { BigBuffer buffer(16); char* b1 = buffer.NextBlock<char>(8); - EXPECT_NE(nullptr, b1); + EXPECT_THAT(b1, NotNull()); char* b2 = buffer.NextBlock<char>(4); - EXPECT_NE(nullptr, b2); + EXPECT_THAT(b2, NotNull()); EXPECT_EQ(b1 + 8, b2); } @@ -42,7 +44,7 @@ TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) { TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) { BigBuffer buffer(16); - EXPECT_NE(nullptr, buffer.NextBlock<char>(32)); + EXPECT_THAT(buffer.NextBlock<char>(32), NotNull()); EXPECT_EQ(32u, buffer.size()); } @@ -50,13 +52,13 @@ TEST(BigBufferTest, AppendAndMoveBlock) { BigBuffer buffer(16); uint32_t* b1 = buffer.NextBlock<uint32_t>(); - ASSERT_NE(nullptr, b1); + ASSERT_THAT(b1, NotNull()); *b1 = 33; { BigBuffer buffer2(16); b1 = buffer2.NextBlock<uint32_t>(); - ASSERT_NE(nullptr, b1); + ASSERT_THAT(b1, NotNull()); *b1 = 44; buffer.AppendBuffer(std::move(buffer2)); @@ -83,7 +85,7 @@ TEST(BigBufferTest, AppendAndMoveBlock) { TEST(BigBufferTest, PadAndAlignProperly) { BigBuffer buffer(16); - ASSERT_NE(buffer.NextBlock<char>(2), nullptr); + ASSERT_THAT(buffer.NextBlock<char>(2), NotNull()); ASSERT_EQ(2u, buffer.size()); buffer.Pad(2); ASSERT_EQ(4u, buffer.size()); diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp index ca14793dd5c3..2057ddcc9e45 100644 --- a/tools/aapt2/util/Maybe_test.cpp +++ b/tools/aapt2/util/Maybe_test.cpp @@ -80,22 +80,22 @@ struct Dummy { TEST(MaybeTest, MakeNothing) { Maybe<int> val = make_nothing<int>(); - AAPT_EXPECT_FALSE(val); + EXPECT_FALSE(val); Maybe<std::string> val2 = make_nothing<std::string>(); - AAPT_EXPECT_FALSE(val2); + EXPECT_FALSE(val2); val2 = make_nothing<std::string>(); - AAPT_EXPECT_FALSE(val2); + EXPECT_FALSE(val2); } TEST(MaybeTest, MakeSomething) { Maybe<int> val = make_value(23); - AAPT_ASSERT_TRUE(val); + ASSERT_TRUE(val); EXPECT_EQ(23, val.value()); Maybe<std::string> val2 = make_value(std::string("hey")); - AAPT_ASSERT_TRUE(val2); + ASSERT_TRUE(val2); EXPECT_EQ(std::string("hey"), val2.value()); } diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 8a8be858cb4c..51a75d7556ad 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -16,19 +16,21 @@ #include "util/Util.h" -#include <utils/Unicode.h> #include <algorithm> #include <ostream> #include <string> #include <vector> #include "androidfw/StringPiece.h" +#include "utils/Unicode.h" +#include "text/Utf8Iterator.h" #include "util/BigBuffer.h" #include "util/Maybe.h" -using android::StringPiece; -using android::StringPiece16; +using ::aapt::text::Utf8Iterator; +using ::android::StringPiece; +using ::android::StringPiece16; namespace aapt { namespace util { @@ -283,33 +285,46 @@ bool VerifyJavaStringFormat(const StringPiece& str) { return true; } -static Maybe<std::string> ParseUnicodeCodepoint(const char** start, - const char* end) { +static bool AppendCodepointToUtf8String(char32_t codepoint, std::string* output) { + ssize_t len = utf32_to_utf8_length(&codepoint, 1); + if (len < 0) { + return false; + } + + const size_t start_append_pos = output->size(); + + // Make room for the next character. + output->resize(output->size() + len); + + char* dst = &*(output->begin() + start_append_pos); + utf32_to_utf8(&codepoint, 1, dst, len + 1); + return true; +} + +static bool AppendUnicodeCodepoint(Utf8Iterator* iter, std::string* output) { char32_t code = 0; - for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) { - char c = **start; + for (size_t i = 0; i < 4 && iter->HasNext(); i++) { + char32_t codepoint = iter->Next(); char32_t a; - if (c >= '0' && c <= '9') { - a = c - '0'; - } else if (c >= 'a' && c <= 'f') { - a = c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - a = c - 'A' + 10; + if (codepoint >= U'0' && codepoint <= U'9') { + a = codepoint - U'0'; + } else if (codepoint >= U'a' && codepoint <= U'f') { + a = codepoint - U'a' + 10; + } else if (codepoint >= U'A' && codepoint <= U'F') { + a = codepoint - U'A' + 10; } else { return {}; } code = (code << 4) | a; } + return AppendCodepointToUtf8String(code, output); +} - ssize_t len = utf32_to_utf8_length(&code, 1); - if (len < 0) { - return {}; +static bool IsCodepointSpace(char32_t codepoint) { + if (static_cast<uint32_t>(codepoint) & 0xffffff00u) { + return false; } - - std::string result_utf8; - result_utf8.resize(len); - utf32_to_utf8(&code, 1, &*result_utf8.begin(), len + 1); - return result_utf8; + return isspace(static_cast<char>(codepoint)); } StringBuilder::StringBuilder(bool preserve_spaces) : preserve_spaces_(preserve_spaces) { @@ -321,57 +336,46 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } // Where the new data will be appended to. - size_t new_data_index = str_.size(); + const size_t new_data_index = str_.size(); + + Utf8Iterator iter(str); + while (iter.HasNext()) { + const char32_t codepoint = iter.Next(); - const char* const end = str.end(); - const char* start = str.begin(); - const char* current = start; - while (current != end) { if (last_char_was_escape_) { - switch (*current) { - case 't': + switch (codepoint) { + case U't': str_ += '\t'; break; - case 'n': + + case U'n': str_ += '\n'; break; - case '#': - str_ += '#'; - break; - case '@': - str_ += '@'; - break; - case '?': - str_ += '?'; - break; - case '"': - str_ += '"'; - break; - case '\'': - str_ += '\''; - break; - case '\\': - str_ += '\\'; + + case U'#': + case U'@': + case U'?': + case U'"': + case U'\'': + case U'\\': + str_ += static_cast<char>(codepoint); break; - case 'u': { - current++; - Maybe<std::string> c = ParseUnicodeCodepoint(¤t, end); - if (!c) { + + case U'u': + if (!AppendUnicodeCodepoint(&iter, &str_)) { error_ = "invalid unicode escape sequence"; return *this; } - str_ += c.value(); - current -= 1; break; - } default: - // Ignore. + // Ignore the escape character and just include the codepoint. + AppendCodepointToUtf8String(codepoint, &str_); break; } last_char_was_escape_ = false; - start = current + 1; - } else if (!preserve_spaces_ && *current == '"') { + + } else if (!preserve_spaces_ && codepoint == U'"') { if (!quote_ && trailing_space_) { // We found an opening quote, and we have trailing space, so we should append that // space now. @@ -384,13 +388,13 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } } quote_ = !quote_; - str_.append(start, current - start); - start = current + 1; - } else if (!preserve_spaces_ && *current == '\'' && !quote_) { + + } else if (!preserve_spaces_ && codepoint == U'\'' && !quote_) { // This should be escaped. error_ = "unescaped apostrophe"; return *this; - } else if (*current == '\\') { + + } else if (codepoint == U'\\') { // This is an escape sequence, convert to the real value. if (!quote_ && trailing_space_) { // We had trailing whitespace, so @@ -400,40 +404,35 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } trailing_space_ = false; } - str_.append(start, current - start); - start = current + 1; last_char_was_escape_ = true; - } else if (!preserve_spaces_ && !quote_) { - // This is not quoted text, so look for whitespace. - if (isspace(*current)) { - // We found whitespace, see if we have seen some - // before. - if (!trailing_space_) { - // We didn't see a previous adjacent space, - // so mark that we did. + } else { + if (preserve_spaces_ || quote_) { + // Quotes mean everything is taken, including whitespace. + AppendCodepointToUtf8String(codepoint, &str_); + } else { + // This is not quoted text, so we will accumulate whitespace and only emit a single + // character of whitespace if it is followed by a non-whitespace character. + if (IsCodepointSpace(codepoint)) { + // We found whitespace. trailing_space_ = true; - str_.append(start, current - start); - } - - // Keep skipping whitespace. - start = current + 1; - } else if (trailing_space_) { - // We saw trailing space before, so replace all - // that trailing space with one space. - if (!str_.empty()) { - str_ += ' '; + } else { + if (trailing_space_) { + // We saw trailing space before, so replace all + // that trailing space with one space. + if (!str_.empty()) { + str_ += ' '; + } + trailing_space_ = false; + } + AppendCodepointToUtf8String(codepoint, &str_); } - trailing_space_ = false; } } - current++; } - str_.append(start, end - start); // Accumulate the added string's UTF-16 length. - ssize_t len = utf8_to_utf16_length( - reinterpret_cast<const uint8_t*>(str_.data()) + new_data_index, - str_.size() - new_data_index); + ssize_t len = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str_.data()) + new_data_index, + str_.size() - new_data_index); if (len < 0) { error_ = "invalid unicode code point"; return *this; diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index b9ada7704a26..ad3989ea430f 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -70,12 +70,6 @@ bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffi android::StringPiece TrimWhitespace(const android::StringPiece& str); /** - * UTF-16 isspace(). It basically checks for lower range characters that are - * whitespace. - */ -inline bool isspace16(char16_t c) { return c < 0x0080 && isspace(c); } - -/** * Returns an iterator to the first character that is not alpha-numeric and that * is not in the allowedChars set. */ @@ -104,6 +98,16 @@ bool IsJavaPackageName(const android::StringPiece& str); Maybe<std::string> GetFullyQualifiedClassName(const android::StringPiece& package, const android::StringPiece& class_name); +template <typename T> +typename std::enable_if<std::is_arithmetic<T>::value, int>::type compare(const T& a, const T& b) { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } + return 0; +} + /** * Makes a std::unique_ptr<> with the template parameter inferred by the compiler. * This will be present in C++14 and can be removed then. diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index 5cced3e9acab..adb52911ab82 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -136,58 +136,34 @@ TEST(UtilTest, IsJavaPackageName) { } TEST(UtilTest, FullyQualifiedClassName) { - Maybe<std::string> res = util::GetFullyQualifiedClassName("android", ".asdf"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "android.asdf"); - - res = util::GetFullyQualifiedClassName("android", ".a.b"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "android.a.b"); - - res = util::GetFullyQualifiedClassName("android", "a.b"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "a.b"); - - res = util::GetFullyQualifiedClassName("", "a.b"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "a.b"); - - res = util::GetFullyQualifiedClassName("android", "Class"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "android.Class"); - - res = util::GetFullyQualifiedClassName("", ""); - AAPT_ASSERT_FALSE(res); - - res = util::GetFullyQualifiedClassName("android", "./Apple"); - AAPT_ASSERT_FALSE(res); + EXPECT_THAT(util::GetFullyQualifiedClassName("android", ".asdf"), Eq("android.asdf")); + EXPECT_THAT(util::GetFullyQualifiedClassName("android", ".a.b"), Eq("android.a.b")); + EXPECT_THAT(util::GetFullyQualifiedClassName("android", "a.b"), Eq("a.b")); + EXPECT_THAT(util::GetFullyQualifiedClassName("", "a.b"), Eq("a.b")); + EXPECT_THAT(util::GetFullyQualifiedClassName("android", "Class"), Eq("android.Class")); + EXPECT_FALSE(util::GetFullyQualifiedClassName("", "")); + EXPECT_FALSE(util::GetFullyQualifiedClassName("android", "./Apple")); } TEST(UtilTest, ExtractResourcePathComponents) { StringPiece prefix, entry, suffix; - ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.xml", - &prefix, &entry, &suffix)); - EXPECT_EQ(prefix, "res/xml-sw600dp/"); - EXPECT_EQ(entry, "entry"); - EXPECT_EQ(suffix, ".xml"); - - ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.9.png", - &prefix, &entry, &suffix)); - - EXPECT_EQ(prefix, "res/xml-sw600dp/"); - EXPECT_EQ(entry, "entry"); - EXPECT_EQ(suffix, ".9.png"); - - EXPECT_FALSE(util::ExtractResFilePathParts("AndroidManifest.xml", &prefix, - &entry, &suffix)); - EXPECT_FALSE( - util::ExtractResFilePathParts("res/.xml", &prefix, &entry, &suffix)); - - ASSERT_TRUE( - util::ExtractResFilePathParts("res//.", &prefix, &entry, &suffix)); - EXPECT_EQ(prefix, "res//"); - EXPECT_EQ(entry, ""); - EXPECT_EQ(suffix, "."); + ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.xml", &prefix, &entry, &suffix)); + EXPECT_THAT(prefix, Eq("res/xml-sw600dp/")); + EXPECT_THAT(entry, Eq("entry")); + EXPECT_THAT(suffix, Eq(".xml")); + + ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.9.png", &prefix, &entry, &suffix)); + EXPECT_THAT(prefix, Eq("res/xml-sw600dp/")); + EXPECT_THAT(entry, Eq("entry")); + EXPECT_THAT(suffix, Eq(".9.png")); + + ASSERT_TRUE(util::ExtractResFilePathParts("res//.", &prefix, &entry, &suffix)); + EXPECT_THAT(prefix, Eq("res//")); + EXPECT_THAT(entry, Eq("")); + EXPECT_THAT(suffix, Eq(".")); + + EXPECT_FALSE(util::ExtractResFilePathParts("AndroidManifest.xml", &prefix, &entry, &suffix)); + EXPECT_FALSE(util::ExtractResFilePathParts("res/.xml", &prefix, &entry, &suffix)); } TEST(UtilTest, VerifyJavaStringFormat) { diff --git a/tools/aapt2/xml/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp index 7110c90fa3a9..0fe7ab05ceb2 100644 --- a/tools/aapt2/xml/XmlActionExecutor_test.cpp +++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp @@ -18,6 +18,8 @@ #include "test/Test.h" +using ::testing::NotNull; + namespace aapt { namespace xml { @@ -42,12 +44,11 @@ TEST(XmlActionExecutorTest, BuildsAccessibleNestedPattern) { test::BuildXmlDom("<manifest><application /></manifest>"); StdErrDiagnostics diag; - ASSERT_TRUE( - executor.Execute(XmlActionExecutorPolicy::kNone, &diag, doc.get())); - ASSERT_NE(nullptr, manifest_el); + ASSERT_TRUE(executor.Execute(XmlActionExecutorPolicy::kNone, &diag, doc.get())); + ASSERT_THAT(manifest_el, NotNull()); EXPECT_EQ(std::string("manifest"), manifest_el->name); - ASSERT_NE(nullptr, application_el); + ASSERT_THAT(application_el, NotNull()); EXPECT_EQ(std::string("application"), application_el->name); } @@ -58,8 +59,7 @@ TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) { std::unique_ptr<XmlResource> doc = test::BuildXmlDom("<manifest><application /><activity /></manifest>"); StdErrDiagnostics diag; - ASSERT_FALSE( - executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); + ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); } } // namespace xml diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index 031801e1dd2f..f0122e8c617a 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -21,6 +21,10 @@ #include "test/Test.h" +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::SizeIs; + namespace aapt { constexpr const char* kXmlPreamble = @@ -40,12 +44,12 @@ TEST(XmlDomTest, Inflate) { const Source source("test.xml"); StdErrDiagnostics diag; std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, source); - ASSERT_NE(doc, nullptr); + ASSERT_THAT(doc, NotNull()); xml::Namespace* ns = xml::NodeCast<xml::Namespace>(doc->root.get()); - ASSERT_NE(ns, nullptr); - EXPECT_EQ(ns->namespace_uri, xml::kSchemaAndroid); - EXPECT_EQ(ns->namespace_prefix, "android"); + ASSERT_THAT(ns, NotNull()); + EXPECT_THAT(ns->namespace_uri, Eq(xml::kSchemaAndroid)); + EXPECT_THAT(ns->namespace_prefix, Eq("android")); } // Escaping is handled after parsing of the values for resource-specific values. @@ -53,31 +57,34 @@ TEST(XmlDomTest, ForwardEscapes) { std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( <element value="\?hello" pattern="\\d{5}">\\d{5}</element>)"); - xml::Element* el = xml::FindRootElement(doc->root.get()); - ASSERT_NE(nullptr, el); + xml::Element* el = xml::FindRootElement(doc.get()); + ASSERT_THAT(el, NotNull()); xml::Attribute* attr = el->FindAttribute({}, "pattern"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("\\\\d{5}", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, Eq("\\\\d{5}")); attr = el->FindAttribute({}, "value"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("\\?hello", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, Eq("\\?hello")); + + ASSERT_THAT(el->children, SizeIs(1u)); xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get()); - ASSERT_NE(nullptr, text); - EXPECT_EQ("\\\\d{5}", text->text); + ASSERT_THAT(text, NotNull()); + EXPECT_THAT(text->text, Eq("\\\\d{5}")); } TEST(XmlDomTest, XmlEscapeSequencesAreParsed) { std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element value=""" />)"); xml::Element* el = xml::FindRootElement(doc.get()); - ASSERT_NE(nullptr, el); + ASSERT_THAT(el, NotNull()); xml::Attribute* attr = el->FindAttribute({}, "value"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("\"", attr->value); + ASSERT_THAT(attr, NotNull()); + + EXPECT_THAT(attr->value, Eq("\"")); } } // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp index 5eecc8f5fb20..cbded8ffac8e 100644 --- a/tools/aapt2/xml/XmlUtil_test.cpp +++ b/tools/aapt2/xml/XmlUtil_test.cpp @@ -21,37 +21,30 @@ namespace aapt { TEST(XmlUtilTest, ExtractPackageFromNamespace) { - AAPT_ASSERT_FALSE(xml::ExtractPackageFromNamespace("com.android")); - AAPT_ASSERT_FALSE( - xml::ExtractPackageFromNamespace("http://schemas.android.com/apk")); - AAPT_ASSERT_FALSE( - xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res")); - AAPT_ASSERT_FALSE( - xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/")); - AAPT_ASSERT_FALSE(xml::ExtractPackageFromNamespace( - "http://schemas.android.com/apk/prv/res/")); + ASSERT_FALSE(xml::ExtractPackageFromNamespace("com.android")); + ASSERT_FALSE(xml::ExtractPackageFromNamespace("http://schemas.android.com/apk")); + ASSERT_FALSE(xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res")); + ASSERT_FALSE(xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/")); + ASSERT_FALSE(xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/prv/res/")); Maybe<xml::ExtractedPackage> p = xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/a"); - AAPT_ASSERT_TRUE(p); + ASSERT_TRUE(p); EXPECT_EQ(std::string("a"), p.value().package); EXPECT_FALSE(p.value().private_namespace); - p = xml::ExtractPackageFromNamespace( - "http://schemas.android.com/apk/prv/res/android"); - AAPT_ASSERT_TRUE(p); + p = xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/prv/res/android"); + ASSERT_TRUE(p); EXPECT_EQ(std::string("android"), p.value().package); EXPECT_TRUE(p.value().private_namespace); - p = xml::ExtractPackageFromNamespace( - "http://schemas.android.com/apk/prv/res/com.test"); - AAPT_ASSERT_TRUE(p); + p = xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/prv/res/com.test"); + ASSERT_TRUE(p); EXPECT_EQ(std::string("com.test"), p.value().package); EXPECT_TRUE(p.value().private_namespace); - p = xml::ExtractPackageFromNamespace( - "http://schemas.android.com/apk/res-auto"); - AAPT_ASSERT_TRUE(p); + p = xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res-auto"); + ASSERT_TRUE(p); EXPECT_EQ(std::string(), p.value().package); EXPECT_TRUE(p.value().private_namespace); } diff --git a/tools/bit/adb.cpp b/tools/bit/adb.cpp index 0c8424de566d..c8faf5c66722 100644 --- a/tools/bit/adb.cpp +++ b/tools/bit/adb.cpp @@ -283,10 +283,19 @@ run_instrumentation_test(const string& packageName, const string& runner, const cmd.AddArg("instrument"); cmd.AddArg("-w"); cmd.AddArg("-m"); - if (className.length() > 0) { - cmd.AddArg("-e"); - cmd.AddArg("class"); - cmd.AddArg(className); + const int classLen = className.length(); + if (classLen > 0) { + if (classLen > 1 && className[classLen - 1] == '.') { + cmd.AddArg("-e"); + cmd.AddArg("package"); + + // "am" actually accepts without removing the last ".", but for cleanlines... + cmd.AddArg(className.substr(0, classLen - 1)); + } else { + cmd.AddArg("-e"); + cmd.AddArg("class"); + cmd.AddArg(className); + } } cmd.AddArg(packageName + "/" + runner); diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp index d056ba53ca84..91ca5143965e 100644 --- a/tools/bit/main.cpp +++ b/tools/bit/main.cpp @@ -50,6 +50,7 @@ struct Target { int testPassCount; int testFailCount; + int unknownFailureCount; // unknown failure == "Process crashed", etc. bool actionsWithNoTests; Target(bool b, bool i, bool t, const string& p); @@ -63,6 +64,7 @@ Target::Target(bool b, bool i, bool t, const string& p) testActionCount(0), testPassCount(0), testFailCount(0), + unknownFailureCount(0), actionsWithNoTests(false) { } @@ -188,8 +190,13 @@ public: */ void SetCurrentAction(TestAction* action); + bool IsSuccess(); + + string GetErrorMessage(); + private: TestAction* m_currentAction; + SessionStatus m_sessionStatus; }; void @@ -241,8 +248,10 @@ TestResults::OnTestStatus(TestStatus& status) } line << ": " << m_currentAction->target->name << ':' << className << "\\#" << testName; print_one_line("%s", line.str().c_str()); - } else if (resultCode == -2) { + } else if ((resultCode == -1) || (resultCode == -2)) { // test failed + // Note -2 means an assertion failure, and -1 means other exceptions. We just treat them + // all as "failures". m_currentAction->failCount++; m_currentAction->target->testFailCount++; printf("%s\n%sFailed: %s:%s\\#%s%s\n", g_escapeClearLine, g_escapeRedBold, @@ -257,9 +266,13 @@ TestResults::OnTestStatus(TestStatus& status) } void -TestResults::OnSessionStatus(SessionStatus& /*status*/) +TestResults::OnSessionStatus(SessionStatus& status) { //status.PrintDebugString(); + m_sessionStatus = status; + if (m_currentAction && !IsSuccess()) { + m_currentAction->target->unknownFailureCount++; + } } void @@ -268,6 +281,24 @@ TestResults::SetCurrentAction(TestAction* action) m_currentAction = action; } +bool +TestResults::IsSuccess() +{ + return m_sessionStatus.result_code() == -1; // Activity.RESULT_OK. +} + +string +TestResults::GetErrorMessage() +{ + bool found; + string shortMsg = get_bundle_string(m_sessionStatus.results(), &found, "shortMsg", NULL); + if (!found) { + return IsSuccess() ? "" : "Unknown failure"; + } + return shortMsg; +} + + /** * Prints the usage statement / help text. */ @@ -342,6 +373,10 @@ print_usage(FILE* out) { fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n"); fprintf(out, " and testRepeated test methods on that class.\n"); fprintf(out, "\n"); + fprintf(out, " bit CtsProtoTestCases:android.util.proto.cts.\n"); + fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the tests in the java package\n"); + fprintf(out, " \"android.util.proto.cts\".\n"); + fprintf(out, "\n"); fprintf(out, " Launching an Activity\n"); fprintf(out, " ---------------------\n"); fprintf(out, " To launch an activity, specify the activity class name after\n"); @@ -564,7 +599,7 @@ check_device_property(const string& property, const string& expected) /** * Run the build, install, and test actions. */ -void +bool run_phases(vector<Target*> targets, const Options& options) { int err = 0; @@ -731,7 +766,7 @@ run_phases(vector<Target*> targets, const Options& options) InstallApk& apk = installApks[i]; if (!apk.file.fileInfo.exists || apk.file.HasChanged()) { // It didn't exist before or it changed, so int needs install - err = run_adb("install", "-r", apk.file.filename.c_str(), NULL); + err = run_adb("install", "-r", "-g", apk.file.filename.c_str(), NULL); check_error(err); apk.installed = true; } else { @@ -833,6 +868,10 @@ run_phases(vector<Target*> targets, const Options& options) printf("%s%d passed%s, %d failed\n", g_escapeGreenBold, action.passCount, g_escapeEndColor, action.failCount); } + if (!testResults.IsSuccess()) { + printf("\n%sTest didn't finish successfully: %s%s\n", g_escapeRedBold, + testResults.GetErrorMessage().c_str(), g_escapeEndColor); + } } } @@ -903,6 +942,7 @@ run_phases(vector<Target*> targets, const Options& options) } // Tests + bool hasErrors = false; if (testActions.size() > 0) { printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor); size_t maxNameLength = 0; @@ -920,12 +960,18 @@ run_phases(vector<Target*> targets, const Options& options) Target* target = targets[i]; if (target->testActionCount > 0) { printf(" %s%s", target->name.c_str(), padding.c_str() + target->name.length()); - if (target->actionsWithNoTests) { + if (target->unknownFailureCount > 0) { + printf(" %sUnknown failure, see above message.%s\n", + g_escapeRedBold, g_escapeEndColor); + hasErrors = true; + } else if (target->actionsWithNoTests) { printf(" %s%d passed, %d failed%s\n", g_escapeYellowBold, target->testPassCount, target->testFailCount, g_escapeEndColor); + hasErrors = true; } else if (target->testFailCount > 0) { printf(" %d passed, %s%d failed%s\n", target->testPassCount, g_escapeRedBold, target->testFailCount, g_escapeEndColor); + hasErrors = true; } else { printf(" %s%d passed%s, %d failed\n", g_escapeGreenBold, target->testPassCount, g_escapeEndColor, target->testFailCount); @@ -940,6 +986,7 @@ run_phases(vector<Target*> targets, const Options& options) } printf("%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor); + return !hasErrors; } /** @@ -987,7 +1034,7 @@ main(int argc, const char** argv) exit(0); } else { // Normal run - run_phases(options.targets, options); + exit(run_phases(options.targets, options) ? 0 : 1); } return 0; diff --git a/tools/incident_report/Android.mk b/tools/incident_report/Android.mk index e57a95949e5a..9e56e3d7eeaf 100644 --- a/tools/incident_report/Android.mk +++ b/tools/incident_report/Android.mk @@ -34,9 +34,6 @@ LOCAL_SHARED_LIBRARIES := \ libplatformprotos \ libprotobuf-cpp-full -# b/34740546, work around clang-tidy segmentation fault. -LOCAL_TIDY_CHECKS := -modernize* - LOCAL_C_FLAGS := \ -Wno-unused-parameter include $(BUILD_HOST_EXECUTABLE) diff --git a/tools/incident_section_gen/Android.mk b/tools/incident_section_gen/Android.mk index 05490266576a..acf3f8327b5c 100644 --- a/tools/incident_section_gen/Android.mk +++ b/tools/incident_section_gen/Android.mk @@ -21,8 +21,6 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := incident-section-gen -# b/34740546, work around clang-tidy segmentation fault. -LOCAL_TIDY_CHECKS := -modernize* LOCAL_CFLAGS += -g -O0 LOCAL_C_INCLUDES := \ external/protobuf/src |