diff options
Diffstat (limited to 'tools')
70 files changed, 3492 insertions, 1589 deletions
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/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 46098cbc3aa4..7ff0c7227c9c 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -877,7 +877,16 @@ ConfigDescription ConfigDescription::CopyWithoutSdkVersion() const { } bool ConfigDescription::Dominates(const ConfigDescription& o) const { - if (*this == DefaultConfig() || *this == o) { + if (*this == o) { + return true; + } + + // Locale de-duping is not-trivial, disable for now (b/62409213). + if (diff(o) & CONFIG_LOCALE) { + return false; + } + + if (*this == DefaultConfig()) { return true; } return MatchWithDensity(o) && !o.MatchWithDensity(*this) && diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp index e89c6beb0c57..efc523fcf59b 100644 --- a/tools/aapt2/DominatorTree_test.cpp +++ b/tools/aapt2/DominatorTree_test.cpp @@ -69,14 +69,12 @@ class PrettyPrinter : public DominatorTree::Visitor { TEST(DominatorTreeTest, DefaultDominatesEverything) { const ConfigDescription default_config = {}; const ConfigDescription land_config = test::ParseConfigOrDie("land"); - const ConfigDescription sw600dp_land_config = - test::ParseConfigOrDie("sw600dp-land-v13"); + const ConfigDescription sw600dp_land_config = test::ParseConfigOrDie("sw600dp-land-v13"); std::vector<std::unique_ptr<ResourceConfigValue>> configs; configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(land_config, "")); - configs.push_back( - util::make_unique<ResourceConfigValue>(sw600dp_land_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_land_config, "")); DominatorTree tree(configs); PrettyPrinter printer; @@ -91,16 +89,13 @@ TEST(DominatorTreeTest, DefaultDominatesEverything) { TEST(DominatorTreeTest, ProductsAreDominatedSeparately) { const ConfigDescription default_config = {}; const ConfigDescription land_config = test::ParseConfigOrDie("land"); - const ConfigDescription sw600dp_land_config = - test::ParseConfigOrDie("sw600dp-land-v13"); + const ConfigDescription sw600dp_land_config = test::ParseConfigOrDie("sw600dp-land-v13"); std::vector<std::unique_ptr<ResourceConfigValue>> configs; configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(land_config, "")); - configs.push_back( - util::make_unique<ResourceConfigValue>(default_config, "phablet")); - configs.push_back( - util::make_unique<ResourceConfigValue>(sw600dp_land_config, "phablet")); + configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "phablet")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_land_config, "phablet")); DominatorTree tree(configs); PrettyPrinter printer; @@ -118,16 +113,11 @@ TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) { const ConfigDescription en_config = test::ParseConfigOrDie("en"); const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21"); const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl-v4"); - const ConfigDescription ldrtl_xhdpi_config = - test::ParseConfigOrDie("ldrtl-xhdpi-v4"); - const ConfigDescription sw300dp_config = - test::ParseConfigOrDie("sw300dp-v13"); - const ConfigDescription sw540dp_config = - test::ParseConfigOrDie("sw540dp-v14"); - const ConfigDescription sw600dp_config = - test::ParseConfigOrDie("sw600dp-v14"); - const ConfigDescription sw720dp_config = - test::ParseConfigOrDie("sw720dp-v13"); + const ConfigDescription ldrtl_xhdpi_config = test::ParseConfigOrDie("ldrtl-xhdpi-v4"); + const ConfigDescription sw300dp_config = test::ParseConfigOrDie("sw300dp-v13"); + const ConfigDescription sw540dp_config = test::ParseConfigOrDie("sw540dp-v14"); + const ConfigDescription sw600dp_config = test::ParseConfigOrDie("sw600dp-v14"); + const ConfigDescription sw720dp_config = test::ParseConfigOrDie("sw720dp-v13"); const ConfigDescription v20_config = test::ParseConfigOrDie("v20"); std::vector<std::unique_ptr<ResourceConfigValue>> configs; @@ -135,8 +125,7 @@ TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) { configs.push_back(util::make_unique<ResourceConfigValue>(en_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(en_v21_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(ldrtl_config, "")); - configs.push_back( - util::make_unique<ResourceConfigValue>(ldrtl_xhdpi_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(ldrtl_xhdpi_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(sw300dp_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(sw540dp_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_config, "")); @@ -148,15 +137,37 @@ TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) { std::string expected = "<default>\n" - " en\n" - " en-v21\n" " ldrtl-v4\n" " ldrtl-xhdpi-v4\n" " sw300dp-v13\n" " sw540dp-v14\n" " sw600dp-v14\n" " sw720dp-v13\n" - " v20\n"; + " v20\n" + "en\n" + " en-v21\n"; + EXPECT_EQ(expected, printer.ToString(&tree)); +} + +TEST(DominatorTreeTest, LocalesAreNeverDominated) { + const ConfigDescription fr_config = test::ParseConfigOrDie("fr"); + const ConfigDescription fr_rCA_config = test::ParseConfigOrDie("fr-rCA"); + const ConfigDescription fr_rFR_config = test::ParseConfigOrDie("fr-rFR"); + + std::vector<std::unique_ptr<ResourceConfigValue>> configs; + configs.push_back(util::make_unique<ResourceConfigValue>(ConfigDescription::DefaultConfig(), "")); + configs.push_back(util::make_unique<ResourceConfigValue>(fr_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(fr_rCA_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(fr_rFR_config, "")); + + DominatorTree tree(configs); + PrettyPrinter printer; + + std::string expected = + "<default>\n" + "fr\n" + "fr-rCA\n" + "fr-rFR\n"; EXPECT_EQ(expected, printer.ToString(&tree)); } 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_test.cpp b/tools/aapt2/ResourceParser_test.cpp index c6382b177f42..d47a529036bc 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->str, 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->str, 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..6e6a2ba6fc50 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 {}; 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..947e091e2d48 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -533,75 +533,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/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 740a401f9b57..e6bf3a6f9f56 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -989,7 +989,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/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/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_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 0711749d0378..bfebedef2a1e 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -257,9 +257,11 @@ class XmlFlattenerVisitor : public xml::Visitor { // Process plain strings to make sure they get properly escaped. StringPiece raw_value = xml_attr->value; - util::StringBuilder str_builder; + + util::StringBuilder str_builder(true /*preserve_spaces*/); + str_builder.Append(xml_attr->value); + if (!options_.keep_raw_values) { - str_builder.Append(xml_attr->value); raw_value = str_builder.ToString(); } diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp index f1e903f2151e..a57e3178accd 100644 --- a/tools/aapt2/flatten/XmlFlattener_test.cpp +++ b/tools/aapt2/flatten/XmlFlattener_test.cpp @@ -23,7 +23,13 @@ #include "util/BigBuffer.h" #include "util/Util.h" -using android::StringPiece16; +using ::aapt::test::StrEq; +using ::android::StringPiece16; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::IsNull; +using ::testing::Ne; +using ::testing::NotNull; namespace aapt { @@ -72,163 +78,138 @@ class XmlFlattenerTest : public ::testing::Test { }; TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <View xmlns:test="http://com.test" - attr="hey"> - <Layout test:hello="hi" /> - <Layout>Some text\\</Layout> - </View>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <View xmlns:test="http://com.test" attr="hey"> + <Layout test:hello="hi" /> + <Layout>Some text\\</Layout> + </View>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); - - ASSERT_EQ(android::ResXMLTree::START_NAMESPACE, tree.next()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE)); size_t len; - const char16_t* namespace_prefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len)); - - const char16_t* namespace_uri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len)); - - ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); - - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - const char16_t* tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len)); - - ASSERT_EQ(1u, tree.getAttributeCount()); - ASSERT_EQ(nullptr, tree.getAttributeNamespace(0, &len)); - const char16_t* attr_name = tree.getAttributeName(0, &len); - EXPECT_EQ(StringPiece16(u"attr"), StringPiece16(attr_name, len)); - - EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size())); + EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test")); + EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test")); - ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"View")); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); + ASSERT_THAT(tree.getAttributeCount(), Eq(1u)); + EXPECT_THAT(tree.getAttributeNamespace(0, &len), IsNull()); + EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"attr")); - ASSERT_EQ(1u, tree.getAttributeCount()); - const char16_t* attr_namespace = tree.getAttributeNamespace(0, &len); - EXPECT_EQ(StringPiece16(u"http://com.test"), StringPiece16(attr_namespace, len)); + const StringPiece16 kAttr(u"attr"); + EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kAttr.data(), kAttr.size()), Eq(0)); - attr_name = tree.getAttributeName(0, &len); - EXPECT_EQ(StringPiece16(u"hello"), StringPiece16(attr_name, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout")); - ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); - ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); + ASSERT_THAT(tree.getAttributeCount(), Eq(1u)); + EXPECT_THAT(tree.getAttributeNamespace(0, &len), StrEq(u"http://com.test")); + EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"hello")); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); - ASSERT_EQ(0u, tree.getAttributeCount()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); - ASSERT_EQ(android::ResXMLTree::TEXT, tree.next()); - const char16_t* text = tree.getText(&len); - EXPECT_EQ(StringPiece16(u"Some text\\"), StringPiece16(text, len)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout")); + ASSERT_THAT(tree.getAttributeCount(), Eq(0u)); - ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"Some text\\")); - ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout")); - ASSERT_EQ(android::ResXMLTree::END_NAMESPACE, tree.next()); - namespace_prefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"View")); - namespace_uri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_NAMESPACE)); + EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test")); + EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test")); - ASSERT_EQ(android::ResXMLTree::END_DOCUMENT, tree.next()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_DOCUMENT)); } TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <View xmlns:tools="http://schemas.android.com/tools" - xmlns:foo="http://schemas.android.com/foo" - foo:bar="Foo" - tools:ignore="MissingTranslation"/>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <View xmlns:tools="http://schemas.android.com/tools" + xmlns:foo="http://schemas.android.com/foo" + foo:bar="Foo" + tools:ignore="MissingTranslation"/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); - - ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE)); size_t len; - const char16_t* namespace_prefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(namespace_prefix, len), u"foo"); - - const char16_t* namespace_uri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(namespace_uri, len), - u"http://schemas.android.com/foo"); - - ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"foo")); + EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://schemas.android.com/foo")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); - EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"), - android::NAME_NOT_FOUND); - EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0); + EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"), + Eq(android::NAME_NOT_FOUND)); + EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), Ge(0)); } TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@id/id" - class="str" - style="@id/id"/>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/id" + class="str" + style="@id/id"/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } - EXPECT_EQ(tree.indexOfClass(), 0); - EXPECT_EQ(tree.indexOfStyle(), 1); + EXPECT_THAT(tree.indexOfClass(), Eq(0)); + EXPECT_THAT(tree.indexOfStyle(), Eq(1)); } // The device ResXMLParser in libandroidfw differentiates between empty namespace and null // namespace. TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"android\"/>"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package="android"/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } const StringPiece16 kPackage = u"package"; - EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); + EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), Ge(0)); } TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"\"/>"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package=""/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } const StringPiece16 kPackage = u"package"; ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); - ASSERT_GE(idx, 0); + ASSERT_THAT(idx, Ge(0)); size_t len; - EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), NotNull()); } TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { @@ -236,11 +217,11 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { context_->SetPackageId(0x80); context_->SetNameManglerPolicy({"com.app.test.feature"}); - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( <View xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@id/foo" - app:foo="@id/foo" />)EOF"); + app:foo="@id/foo" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); @@ -253,59 +234,57 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(android::ResXMLTree::BAD_DOCUMENT, tree.getEventType()); - ASSERT_NE(android::ResXMLTree::END_DOCUMENT, tree.getEventType()); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } ssize_t idx; idx = tree.indexOfAttribute(xml::kSchemaAndroid, "id"); - ASSERT_GE(idx, 0); - EXPECT_EQ(idx, tree.indexOfID()); - EXPECT_EQ(ResourceId(0x010100d0), ResourceId(tree.getAttributeNameResID(idx))); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.indexOfID(), Eq(idx)); + EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x010100d0u)); idx = tree.indexOfAttribute(xml::kSchemaAuto, "foo"); - ASSERT_GE(idx, 0); - EXPECT_EQ(ResourceId(0x80010000), ResourceId(tree.getAttributeNameResID(idx))); - EXPECT_EQ(android::Res_value::TYPE_REFERENCE, tree.getAttributeDataType(idx)); - EXPECT_EQ(ResourceId(0x80020000), tree.getAttributeData(idx)); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x80010000u)); + EXPECT_THAT(tree.getAttributeDataType(idx), Eq(android::Res_value::TYPE_REFERENCE)); + EXPECT_THAT(tree.getAttributeData(idx), Eq(int32_t(0x80020000))); } TEST_F(XmlFlattenerTest, ProcessEscapedStrings) { std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom( - R"EOF(<element value="\?hello" pattern="\\d{5}">\\d{5}</element>)EOF"); + R"(<element value="\?hello" pattern="\\d{5}" other=""">\\d{5}</element>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } const StringPiece16 kValue = u"value"; const StringPiece16 kPattern = u"pattern"; + const StringPiece16 kOther = u"other"; size_t len; ssize_t idx; - const char16_t* str16; idx = tree.indexOfAttribute(nullptr, 0, kValue.data(), kValue.size()); - ASSERT_GE(idx, 0); - str16 = tree.getAttributeStringValue(idx, &len); - ASSERT_NE(nullptr, str16); - EXPECT_EQ(StringPiece16(u"?hello"), StringPiece16(str16, len)); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"?hello")); idx = tree.indexOfAttribute(nullptr, 0, kPattern.data(), kPattern.size()); - ASSERT_GE(idx, 0); - str16 = tree.getAttributeStringValue(idx, &len); - ASSERT_NE(nullptr, str16); - EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len)); - - ASSERT_EQ(android::ResXMLTree::TEXT, tree.next()); - str16 = tree.getText(&len); - ASSERT_NE(nullptr, str16); - EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len)); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\\d{5}")); + + idx = tree.indexOfAttribute(nullptr, 0, kOther.data(), kOther.size()); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\"")); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"\\d{5}")); } } // namespace aapt 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/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 99fd95be2571..a0ffefad1e1c 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -309,6 +309,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/optimize/ResourceDeduper_test.cpp b/tools/aapt2/optimize/ResourceDeduper_test.cpp index 4d00fa6efe37..d9f384c095ca 100644 --- a/tools/aapt2/optimize/ResourceDeduper_test.cpp +++ b/tools/aapt2/optimize/ResourceDeduper_test.cpp @@ -19,69 +19,88 @@ #include "ResourceTable.h" #include "test/Test.h" +using ::aapt::test::HasValue; +using ::testing::Not; + namespace aapt { TEST(ResourceDeduperTest, SameValuesAreDeduped) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const ConfigDescription default_config = {}; + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl"); + const ConfigDescription ldrtl_v21_config = test::ParseConfigOrDie("ldrtl-v21"); const ConfigDescription en_config = test::ParseConfigOrDie("en"); const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21"); - // Chosen because this configuration is compatible with en. + // Chosen because this configuration is compatible with ldrtl/en. const ConfigDescription land_config = test::ParseConfigOrDie("land"); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddString("android:string/dedupe", ResourceId{}, default_config, - "dedupe") - .AddString("android:string/dedupe", ResourceId{}, en_config, "dedupe") - .AddString("android:string/dedupe", ResourceId{}, land_config, - "dedupe") - .AddString("android:string/dedupe2", ResourceId{}, default_config, - "dedupe") - .AddString("android:string/dedupe2", ResourceId{}, en_config, - "dedupe") - .AddString("android:string/dedupe2", ResourceId{}, en_v21_config, - "keep") - .AddString("android:string/dedupe2", ResourceId{}, land_config, - "dedupe") + .AddString("android:string/dedupe", ResourceId{}, default_config, "dedupe") + .AddString("android:string/dedupe", ResourceId{}, ldrtl_config, "dedupe") + .AddString("android:string/dedupe", ResourceId{}, land_config, "dedupe") + + .AddString("android:string/dedupe2", ResourceId{}, default_config, "dedupe") + .AddString("android:string/dedupe2", ResourceId{}, ldrtl_config, "dedupe") + .AddString("android:string/dedupe2", ResourceId{}, ldrtl_v21_config, "keep") + .AddString("android:string/dedupe2", ResourceId{}, land_config, "dedupe") + + .AddString("android:string/dedupe3", ResourceId{}, default_config, "dedupe") + .AddString("android:string/dedupe3", ResourceId{}, en_config, "dedupe") + .AddString("android:string/dedupe3", ResourceId{}, en_v21_config, "dedupe") .Build(); ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); - EXPECT_EQ(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/dedupe", en_config)); - EXPECT_EQ(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/dedupe", land_config)); - EXPECT_EQ(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/dedupe2", en_config)); - EXPECT_NE(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/dedupe2", en_v21_config)); + EXPECT_THAT(table, Not(HasValue("android:string/dedupe", ldrtl_config))); + EXPECT_THAT(table, Not(HasValue("android:string/dedupe", land_config))); + + EXPECT_THAT(table, HasValue("android:string/dedupe2", ldrtl_v21_config)); + EXPECT_THAT(table, Not(HasValue("android:string/dedupe2", ldrtl_config))); + + EXPECT_THAT(table, HasValue("android:string/dedupe3", default_config)); + EXPECT_THAT(table, HasValue("android:string/dedupe3", en_config)); + EXPECT_THAT(table, Not(HasValue("android:string/dedupe3", en_v21_config))); } TEST(ResourceDeduperTest, DifferentValuesAreKept) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const ConfigDescription default_config = {}; - const ConfigDescription en_config = test::ParseConfigOrDie("en"); - const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21"); - // Chosen because this configuration is compatible with en. + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl"); + const ConfigDescription ldrtl_v21_config = test::ParseConfigOrDie("ldrtl-v21"); + // Chosen because this configuration is compatible with ldrtl. const ConfigDescription land_config = test::ParseConfigOrDie("land"); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddString("android:string/keep", ResourceId{}, default_config, - "keep") - .AddString("android:string/keep", ResourceId{}, en_config, "keep") - .AddString("android:string/keep", ResourceId{}, en_v21_config, - "keep2") + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, ldrtl_config, "keep") + .AddString("android:string/keep", ResourceId{}, ldrtl_v21_config, "keep2") .AddString("android:string/keep", ResourceId{}, land_config, "keep2") .Build(); ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); - EXPECT_NE(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/keep", en_config)); - EXPECT_NE(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/keep", en_v21_config)); - EXPECT_NE(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/keep", land_config)); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_v21_config)); + EXPECT_THAT(table, HasValue("android:string/keep", land_config)); +} + +TEST(ResourceDeduperTest, LocalesValuesAreKept) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription fr_config = test::ParseConfigOrDie("fr"); + const ConfigDescription fr_rCA_config = test::ParseConfigOrDie("fr-rCA"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, fr_config, "keep") + .AddString("android:string/keep", ResourceId{}, fr_rCA_config, "keep") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); + EXPECT_THAT(table, HasValue("android:string/keep", fr_config)); + EXPECT_THAT(table, HasValue("android:string/keep", fr_rCA_config)); } } // namespace aapt 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 05851485e216..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 { @@ -145,11 +136,34 @@ void PrintTo(const Maybe<T>& value, std::ostream* out) { namespace test { +MATCHER_P(StrEq, a, + std::string(negation ? "isn't" : "is") + " equal to " + + ::testing::PrintToString(android::StringPiece16(a))) { + return android::StringPiece16(arg) == a; +} + MATCHER_P(ValueEq, a, std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { return arg.Equals(&a); } +MATCHER_P(StrValueEq, a, + std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { + return *(arg.value) == a; +} + +MATCHER_P(HasValue, name, + std::string(negation ? "does not have" : "has") + " value " + + ::testing::PrintToString(name)) { + return GetValueForConfig<Value>(&(*arg), name, {}) != nullptr; +} + +MATCHER_P2(HasValue, name, config, + std::string(negation ? "does not have" : "has") + " value " + + ::testing::PrintToString(name) + " for config " + ::testing::PrintToString(config)) { + return GetValueForConfig<Value>(&(*arg), name, config) != nullptr; +} + } // namespace test } // namespace aapt diff --git a/tools/aapt2/text/Unicode.cpp b/tools/aapt2/text/Unicode.cpp new file mode 100644 index 000000000000..38ec9c465ec2 --- /dev/null +++ b/tools/aapt2/text/Unicode.cpp @@ -0,0 +1,114 @@ +/* + * 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; +} + +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..270718734cff --- /dev/null +++ b/tools/aapt2/text/Unicode.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_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 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..0d43353ef39a --- /dev/null +++ b/tools/aapt2/text/Utf8Iterator.cpp @@ -0,0 +1,62 @@ +/* + * 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), next_pos_(0), current_codepoint_(0) { + DoNext(); +} + +void Utf8Iterator::DoNext() { + size_t next_pos = 0u; + int32_t result = utf32_from_utf8_at(str_.data(), str_.size(), next_pos_, &next_pos); + if (result == -1) { + current_codepoint_ = 0u; + } else { + current_codepoint_ = static_cast<char32_t>(result); + next_pos_ = next_pos; + } +} + +bool Utf8Iterator::HasNext() const { + return current_codepoint_ != 0; +} + +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..6923957a5a22 --- /dev/null +++ b/tools/aapt2/text/Utf8Iterator.h @@ -0,0 +1,49 @@ +/* + * 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; + + void Skip(int amount); + + char32_t Next(); + + private: + DISALLOW_COPY_AND_ASSIGN(Utf8Iterator); + + void DoNext(); + + android::StringPiece str_; + 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..f3111c081276 --- /dev/null +++ b/tools/aapt2/text/Utf8Iterator_test.cpp @@ -0,0 +1,67 @@ +/* + * 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 ::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()); +} + +} // 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..d7e0479bb788 --- /dev/null +++ b/tools/aapt2/tools/extract_unicode_properties.py @@ -0,0 +1,98 @@ +#!/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): + prog = re.compile(r"^(?P<first>\w{4})(..(?P<last>\w{4}))?\W+;\W+(?P<prop>\w+)\n$") + chars = {} + 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: + chars[char] = CharacterProperty(char, char, 0) + chars[char].prop_type |= prop_type + + 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) + + with open(sys.argv[1]) as f: + props = {"XID_Start": 1, "XID_Continue": 2} + char_props = extract_unicode_properties(f, props) + print("{}\nconst static std::array<CharacterProperties, {}> sCharacterProperties = {}" + .format(license, len(char_props), "{{")) + for prop in char_props: + print(" {},".format(prop)) + print("}};") + 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.h b/tools/aapt2/util/Maybe.h index b43f8e87fd68..9a82418e0a5a 100644 --- a/tools/aapt2/util/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -281,16 +281,12 @@ inline Maybe<T> make_nothing() { return Maybe<T>(); } -/** - * Define the == operator between Maybe<T> and Maybe<U> only if the operator T - * == U is defined. - * That way the compiler will show an error at the callsite when comparing two - * Maybe<> objects - * whose inner types can't be compared. - */ +// Define the == operator between Maybe<T> and Maybe<U> only if the operator T == U is defined. +// That way the compiler will show an error at the callsite when comparing two Maybe<> objects +// whose inner types can't be compared. template <typename T, typename U> -typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==( - const Maybe<T>& a, const Maybe<U>& b) { +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(const Maybe<T>& a, + const Maybe<U>& b) { if (a && b) { return a.value() == b.value(); } else if (!a && !b) { @@ -299,18 +295,22 @@ typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==( return false; } -/** - * Same as operator== but negated. - */ template <typename T, typename U> -typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=( - const Maybe<T>& a, const Maybe<U>& b) { +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(const Maybe<T>& a, + const U& b) { + return a ? a.value() == b : false; +} + +// Same as operator== but negated. +template <typename T, typename U> +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=(const Maybe<T>& a, + const Maybe<U>& b) { return !(a == b); } template <typename T, typename U> -typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<( - const Maybe<T>& a, const Maybe<U>& b) { +typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<(const Maybe<T>& a, + const Maybe<U>& b) { if (a && b) { return a.value() < b.value(); } else if (!a && !b) { 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 28e952e25a67..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,49 @@ 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; } + return isspace(static_cast<char>(codepoint)); +} - std::string result_utf8; - result_utf8.resize(len); - utf32_to_utf8(&code, 1, &*result_utf8.begin(), len + 1); - return result_utf8; +StringBuilder::StringBuilder(bool preserve_spaces) : preserve_spaces_(preserve_spaces) { } StringBuilder& StringBuilder::Append(const StringPiece& str) { @@ -318,64 +336,51 @@ 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 (*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 + // We found an opening quote, and we have trailing space, so we should append that // space now. if (trailing_space_) { - // We had trailing whitespace, so - // replace with a single space. + // We had trailing whitespace, so replace with a single space. if (!str_.empty()) { str_ += ' '; } @@ -383,13 +388,13 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } } quote_ = !quote_; - str_.append(start, current - start); - start = current + 1; - } else if (*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 @@ -399,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 (!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 386f74b0301a..b9ada7704a26 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -166,6 +166,8 @@ bool VerifyJavaStringFormat(const android::StringPiece& str); class StringBuilder { public: + explicit StringBuilder(bool preserve_spaces = false); + StringBuilder& Append(const android::StringPiece& str); const std::string& ToString() const; const std::string& Error() const; @@ -179,6 +181,7 @@ class StringBuilder { explicit operator bool() const; private: + bool preserve_spaces_; std::string str_; size_t utf16_len_ = 0; bool quote_ = false; diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index e49aee5d50ed..adb52911ab82 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -20,16 +20,17 @@ #include "test/Test.h" -using android::StringPiece; +using ::android::StringPiece; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::SizeIs; namespace aapt { TEST(UtilTest, TrimOnlyWhitespace) { - const std::string full = "\n "; - - StringPiece trimmed = util::TrimWhitespace(full); + const StringPiece trimmed = util::TrimWhitespace("\n "); EXPECT_TRUE(trimmed.empty()); - EXPECT_EQ(0u, trimmed.size()); + EXPECT_THAT(trimmed, SizeIs(0u)); } TEST(UtilTest, StringEndsWith) { @@ -41,85 +42,74 @@ TEST(UtilTest, StringStartsWith) { } TEST(UtilTest, StringBuilderSplitEscapeSequence) { - EXPECT_EQ(StringPiece("this is a new\nline."), util::StringBuilder() - .Append("this is a new\\") - .Append("nline.") - .ToString()); + EXPECT_THAT(util::StringBuilder().Append("this is a new\\").Append("nline.").ToString(), + Eq("this is a new\nline.")); } TEST(UtilTest, StringBuilderWhitespaceRemoval) { - EXPECT_EQ(StringPiece("hey guys this is so cool"), - util::StringBuilder() - .Append(" hey guys ") - .Append(" this is so cool ") - .ToString()); - - EXPECT_EQ(StringPiece(" wow, so many \t spaces. what?"), - util::StringBuilder() - .Append(" \" wow, so many \t ") - .Append("spaces. \"what? ") - .ToString()); - - EXPECT_EQ(StringPiece("where is the pie?"), util::StringBuilder() - .Append(" where \t ") - .Append(" \nis the " - " pie?") - .ToString()); + EXPECT_THAT(util::StringBuilder().Append(" hey guys ").Append(" this is so cool ").ToString(), + Eq("hey guys this is so cool")); + EXPECT_THAT( + util::StringBuilder().Append(" \" wow, so many \t ").Append("spaces. \"what? ").ToString(), + Eq(" wow, so many \t spaces. what?")); + EXPECT_THAT(util::StringBuilder().Append(" where \t ").Append(" \nis the pie?").ToString(), + Eq("where is the pie?")); } TEST(UtilTest, StringBuilderEscaping) { - EXPECT_EQ(StringPiece("hey guys\n this \t is so\\ cool"), - util::StringBuilder() - .Append(" hey guys\\n ") - .Append(" this \\t is so\\\\ cool ") - .ToString()); - - EXPECT_EQ(StringPiece("@?#\\\'"), - util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString()); + EXPECT_THAT(util::StringBuilder() + .Append(" hey guys\\n ") + .Append(" this \\t is so\\\\ cool ") + .ToString(), + Eq("hey guys\n this \t is so\\ cool")); + EXPECT_THAT(util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString(), Eq("@?#\\\'")); } TEST(UtilTest, StringBuilderMisplacedQuote) { - util::StringBuilder builder{}; + util::StringBuilder builder; EXPECT_FALSE(builder.Append("they're coming!")); } TEST(UtilTest, StringBuilderUnicodeCodes) { - EXPECT_EQ(std::string("\u00AF\u0AF0 woah"), - util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString()); - + EXPECT_THAT(util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString(), + Eq("\u00AF\u0AF0 woah")); EXPECT_FALSE(util::StringBuilder().Append("\\u00 yo")); } +TEST(UtilTest, StringBuilderPreserveSpaces) { + EXPECT_THAT(util::StringBuilder(true /*preserve_spaces*/).Append("\"").ToString(), Eq("\"")); +} + TEST(UtilTest, TokenizeInput) { auto tokenizer = util::Tokenize(StringPiece("this| is|the|end"), '|'); auto iter = tokenizer.begin(); - ASSERT_EQ(*iter, StringPiece("this")); + ASSERT_THAT(*iter, Eq("this")); ++iter; - ASSERT_EQ(*iter, StringPiece(" is")); + ASSERT_THAT(*iter, Eq(" is")); ++iter; - ASSERT_EQ(*iter, StringPiece("the")); + ASSERT_THAT(*iter, Eq("the")); ++iter; - ASSERT_EQ(*iter, StringPiece("end")); + ASSERT_THAT(*iter, Eq("end")); ++iter; - ASSERT_EQ(tokenizer.end(), iter); + ASSERT_THAT(iter, Eq(tokenizer.end())); } TEST(UtilTest, TokenizeEmptyString) { auto tokenizer = util::Tokenize(StringPiece(""), '|'); auto iter = tokenizer.begin(); - ASSERT_NE(tokenizer.end(), iter); - ASSERT_EQ(StringPiece(), *iter); + ASSERT_THAT(iter, Ne(tokenizer.end())); + ASSERT_THAT(*iter, Eq(StringPiece())); ++iter; - ASSERT_EQ(tokenizer.end(), iter); + ASSERT_THAT(iter, Eq(tokenizer.end())); } TEST(UtilTest, TokenizeAtEnd) { auto tokenizer = util::Tokenize(StringPiece("one."), '.'); auto iter = tokenizer.begin(); - ASSERT_EQ(*iter, StringPiece("one")); + ASSERT_THAT(*iter, Eq("one")); ++iter; - ASSERT_NE(iter, tokenizer.end()); - ASSERT_EQ(*iter, StringPiece()); + ASSERT_THAT(iter, Ne(tokenizer.end())); + ASSERT_THAT(*iter, Eq(StringPiece())); } TEST(UtilTest, IsJavaClassName) { @@ -146,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 fb18ea316ca6..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 = @@ -28,47 +32,59 @@ constexpr const char* kXmlPreamble = TEST(XmlDomTest, Inflate) { std::stringstream in(kXmlPreamble); - in << R"EOF( - <Layout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <TextView android:id="@+id/id" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - </Layout> - )EOF"; - - const Source source = {"test.xml"}; + in << R"( + <Layout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView android:id="@+id/id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </Layout>)"; + + 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. TEST(XmlDomTest, ForwardEscapes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <element value="\?hello" pattern="\\d{5}">\\d{5}</element>)EOF"); + 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)); - ASSERT_EQ(1u, el->children.size()); 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_THAT(el, NotNull()); + + xml::Attribute* attr = el->FindAttribute({}, "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..a7fbc2eb0206 100644 --- a/tools/bit/main.cpp +++ b/tools/bit/main.cpp @@ -342,6 +342,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"); @@ -731,7 +735,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 { 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 |