diff options
| author | 2017-06-29 17:53:36 -0700 | |
|---|---|---|
| committer | 2017-07-07 13:23:55 -0700 | |
| commit | fba0cf2950a91726e475fb180090cc25bcb11a7a (patch) | |
| tree | 222ec9ccc4e1710e5a269edd2fffce60ca1692d7 | |
| parent | 66ea8400383d5737b996a136f3aead0965f7be3d (diff) | |
AAPT2: Fix processing of quotes in XML
When processing attributes in XML, quotes can't be used to mark a
section as whitespace preserving, so the assumption should be that the
entire string is whitespace preserving, which makes quote characters
literals.
Bug: 62840718
Bug: 62840406
Test: make aapt2_tests
Change-Id: I4afff02148b5b8e78833abf1f323c2f5325d6155
| -rw-r--r-- | libs/androidfw/include/androidfw/StringPiece.h | 1 | ||||
| -rw-r--r-- | tools/aapt2/flatten/XmlFlattener.cpp | 6 | ||||
| -rw-r--r-- | tools/aapt2/flatten/XmlFlattener_test.cpp | 225 | ||||
| -rw-r--r-- | tools/aapt2/readme.md | 2 | ||||
| -rw-r--r-- | tools/aapt2/test/Common.h | 6 | ||||
| -rw-r--r-- | tools/aapt2/util/Maybe.h | 32 | ||||
| -rw-r--r-- | tools/aapt2/util/Util.cpp | 15 | ||||
| -rw-r--r-- | tools/aapt2/util/Util.h | 3 | ||||
| -rw-r--r-- | tools/aapt2/util/Util_test.cpp | 142 | ||||
| -rw-r--r-- | tools/aapt2/xml/XmlDom_test.cpp | 54 |
10 files changed, 233 insertions, 253 deletions
diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h index a873d66803e7..99b424568a1f 100644 --- a/libs/androidfw/include/androidfw/StringPiece.h +++ b/libs/androidfw/include/androidfw/StringPiece.h @@ -37,6 +37,7 @@ class BasicStringPiece { public: using const_iterator = const TChar*; using difference_type = size_t; + using size_type = size_t; // End of string marker. constexpr static const size_t npos = static_cast<size_t>(-1); 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/readme.md b/tools/aapt2/readme.md index 01d4a4160615..e128c13460d7 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -11,6 +11,8 @@ the set of Proguard keep rules. (bug 62216174) - Automatically version XML `<adaptive-icon>` resources to v26. (bug 62316340) - 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 ...` diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 11821031b131..d7b46caf8c94 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -136,6 +136,12 @@ 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); 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/Util.cpp b/tools/aapt2/util/Util.cpp index 9fde1b474bf3..51a75d7556ad 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -327,6 +327,9 @@ static bool IsCodepointSpace(char32_t codepoint) { return isspace(static_cast<char>(codepoint)); } +StringBuilder::StringBuilder(bool preserve_spaces) : preserve_spaces_(preserve_spaces) { +} + StringBuilder& StringBuilder::Append(const StringPiece& str) { if (!error_.empty()) { return *this; @@ -372,14 +375,12 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } last_char_was_escape_ = false; - } else if (codepoint == U'"') { + } 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_ += ' '; } @@ -388,7 +389,7 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } quote_ = !quote_; - } else if (codepoint == U'\'' && !quote_) { + } else if (!preserve_spaces_ && codepoint == U'\'' && !quote_) { // This should be escaped. error_ = "unescaped apostrophe"; return *this; @@ -405,7 +406,7 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } last_char_was_escape_ = true; } else { - if (quote_) { + if (preserve_spaces_ || quote_) { // Quotes mean everything is taken, including whitespace. AppendCodepointToUtf8String(codepoint, &str_); } else { diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 8bca9dd739bf..410258c40209 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -171,6 +171,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; @@ -184,6 +186,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 a09001a71efc..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,52 +136,34 @@ TEST(UtilTest, IsJavaPackageName) { } TEST(UtilTest, FullyQualifiedClassName) { - Maybe<std::string> res = util::GetFullyQualifiedClassName("android", ".asdf"); - ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "android.asdf"); - - res = util::GetFullyQualifiedClassName("android", ".a.b"); - ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "android.a.b"); - - res = util::GetFullyQualifiedClassName("android", "a.b"); - ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "a.b"); - - res = util::GetFullyQualifiedClassName("", "a.b"); - ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "a.b"); - - res = util::GetFullyQualifiedClassName("android", "Class"); - ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "android.Class"); - - res = util::GetFullyQualifiedClassName("", ""); - ASSERT_FALSE(res); - - res = util::GetFullyQualifiedClassName("android", "./Apple"); - 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"); + 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_EQ(prefix, "res/xml-sw600dp/"); - EXPECT_EQ(entry, "entry"); - EXPECT_EQ(suffix, ".9.png"); + 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)); - - ASSERT_TRUE(util::ExtractResFilePathParts("res//.", &prefix, &entry, &suffix)); - EXPECT_EQ(prefix, "res//"); - EXPECT_EQ(entry, ""); - EXPECT_EQ(suffix, "."); } TEST(UtilTest, VerifyJavaStringFormat) { diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index c1aa10b89a2f..f0122e8c617a 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -21,7 +21,9 @@ #include "test/Test.h" +using ::testing::Eq; using ::testing::NotNull; +using ::testing::SizeIs; namespace aapt { @@ -30,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_THAT(doc, NotNull()); xml::Namespace* ns = xml::NodeCast<xml::Namespace>(doc->root.get()); ASSERT_THAT(ns, NotNull()); - EXPECT_EQ(ns->namespace_uri, xml::kSchemaAndroid); - EXPECT_EQ(ns->namespace_prefix, "android"); + 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()); + xml::Element* el = xml::FindRootElement(doc.get()); ASSERT_THAT(el, NotNull()); xml::Attribute* attr = el->FindAttribute({}, "pattern"); ASSERT_THAT(attr, NotNull()); - EXPECT_EQ("\\\\d{5}", attr->value); + EXPECT_THAT(attr->value, Eq("\\\\d{5}")); attr = el->FindAttribute({}, "value"); ASSERT_THAT(attr, NotNull()); - EXPECT_EQ("\\?hello", attr->value); + 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_THAT(text, NotNull()); - EXPECT_EQ("\\\\d{5}", text->text); + 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 |