XmlFlattener_test.cpp revision e1094a2e232277a719025aa5c97c492502c34f5b
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "format/binary/XmlFlattener.h"
18
19#include "androidfw/ResourceTypes.h"
20
21#include "link/Linkers.h"
22#include "test/Test.h"
23#include "util/BigBuffer.h"
24#include "util/Util.h"
25
26using ::aapt::test::StrEq;
27using ::android::StringPiece16;
28using ::testing::Eq;
29using ::testing::Ge;
30using ::testing::IsNull;
31using ::testing::Ne;
32using ::testing::NotNull;
33
34namespace aapt {
35
36class XmlFlattenerTest : public ::testing::Test {
37 public:
38  void SetUp() override {
39    context_ = test::ContextBuilder()
40                   .SetCompilationPackage("com.app.test")
41                   .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"})
42                   .AddSymbolSource(
43                       test::StaticSymbolSourceBuilder()
44                           .AddPublicSymbol("android:attr/id", ResourceId(0x010100d0),
45                                            test::AttributeBuilder().Build())
46                           .AddSymbol("com.app.test:id/id", ResourceId(0x7f020000))
47                           .AddPublicSymbol("android:attr/paddingStart", ResourceId(0x010103b3),
48                                            test::AttributeBuilder().Build())
49                           .AddPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435),
50                                            test::AttributeBuilder().Build())
51                           .AddSymbol("com.app.test.feature:id/foo", ResourceId(0x80020000))
52                           .AddSymbol("com.app.test.feature:attr/foo", ResourceId(0x80010000),
53                                      test::AttributeBuilder().Build())
54                           .Build())
55                   .Build();
56  }
57
58  ::testing::AssertionResult Flatten(xml::XmlResource* doc, android::ResXMLTree* out_tree,
59                                     const XmlFlattenerOptions& options = {}) {
60    using namespace android;  // For NO_ERROR on windows because it is a macro.
61
62    BigBuffer buffer(1024);
63    XmlFlattener flattener(&buffer, options);
64    if (!flattener.Consume(context_.get(), doc)) {
65      return ::testing::AssertionFailure() << "failed to flatten XML Tree";
66    }
67
68    std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
69    if (out_tree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
70      return ::testing::AssertionFailure() << "flattened XML is corrupt";
71    }
72    return ::testing::AssertionSuccess();
73  }
74
75 protected:
76  std::unique_ptr<test::Context> context_;
77};
78
79TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
80  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
81      <View xmlns:test="http://com.test" attr="hey">
82          <Layout test:hello="hi" />
83          <Layout>Some text\\</Layout>
84      </View>)");
85
86  android::ResXMLTree tree;
87  ASSERT_TRUE(Flatten(doc.get(), &tree));
88  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE));
89
90  size_t len;
91  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test"));
92  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test"));
93
94  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
95  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
96  EXPECT_THAT(tree.getElementName(&len), StrEq(u"View"));
97
98  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
99  EXPECT_THAT(tree.getAttributeNamespace(0, &len), IsNull());
100  EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"attr"));
101
102  const StringPiece16 kAttr(u"attr");
103  EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kAttr.data(), kAttr.size()), Eq(0));
104
105  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
106  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
107  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
108
109  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
110  EXPECT_THAT(tree.getAttributeNamespace(0, &len), StrEq(u"http://com.test"));
111  EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"hello"));
112
113  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
114  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
115
116  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
117  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
118  ASSERT_THAT(tree.getAttributeCount(), Eq(0u));
119
120  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT));
121  EXPECT_THAT(tree.getText(&len), StrEq(u"Some text\\"));
122
123  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
124  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
125  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
126
127  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
128  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
129  EXPECT_THAT(tree.getElementName(&len), StrEq(u"View"));
130
131  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_NAMESPACE));
132  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test"));
133  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test"));
134
135  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_DOCUMENT));
136}
137
138TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) {
139  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
140      <View xmlns:tools="http://schemas.android.com/tools"
141          xmlns:foo="http://schemas.android.com/foo"
142          foo:bar="Foo"
143          tools:ignore="MissingTranslation"/>)");
144
145  android::ResXMLTree tree;
146  ASSERT_TRUE(Flatten(doc.get(), &tree));
147  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE));
148
149  size_t len;
150  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"foo"));
151  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://schemas.android.com/foo"));
152  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
153
154  EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
155              Eq(android::NAME_NOT_FOUND));
156  EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), Ge(0));
157}
158
159TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
160  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
161      <View xmlns:android="http://schemas.android.com/apk/res/android"
162          android:id="@id/id"
163          class="str"
164          style="@id/id"/>)");
165
166  android::ResXMLTree tree;
167  ASSERT_TRUE(Flatten(doc.get(), &tree));
168
169  while (tree.next() != android::ResXMLTree::START_TAG) {
170    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
171    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
172  }
173
174  EXPECT_THAT(tree.indexOfClass(), Eq(0));
175  EXPECT_THAT(tree.indexOfStyle(), Eq(1));
176}
177
178// The device ResXMLParser in libandroidfw differentiates between empty namespace and null
179// namespace.
180TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
181  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package="android"/>)");
182
183  android::ResXMLTree tree;
184  ASSERT_TRUE(Flatten(doc.get(), &tree));
185
186  while (tree.next() != android::ResXMLTree::START_TAG) {
187    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
188    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
189  }
190
191  const StringPiece16 kPackage = u"package";
192  EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), Ge(0));
193}
194
195TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) {
196  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package=""/>)");
197
198  android::ResXMLTree tree;
199  ASSERT_TRUE(Flatten(doc.get(), &tree));
200
201  while (tree.next() != android::ResXMLTree::START_TAG) {
202    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
203    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
204  }
205
206  const StringPiece16 kPackage = u"package";
207  ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
208  ASSERT_THAT(idx, Ge(0));
209
210  size_t len;
211  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), NotNull());
212}
213
214TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
215  context_->SetCompilationPackage("com.app.test.feature");
216  context_->SetPackageId(0x80);
217  context_->SetNameManglerPolicy({"com.app.test.feature"});
218
219  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
220      <View xmlns:android="http://schemas.android.com/apk/res/android"
221            xmlns:app="http://schemas.android.com/apk/res-auto"
222            android:id="@id/foo"
223            app:foo="@id/foo" />)");
224
225  XmlReferenceLinker linker;
226  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
227
228  // The tree needs a custom DynamicRefTable since it is not using a standard app ID (0x7f).
229  android::DynamicRefTable dynamic_ref_table;
230  dynamic_ref_table.addMapping(0x80, 0x80);
231
232  android::ResXMLTree tree(&dynamic_ref_table);
233  ASSERT_TRUE(Flatten(doc.get(), &tree));
234
235  while (tree.next() != android::ResXMLTree::START_TAG) {
236    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
237    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
238  }
239
240  ssize_t idx;
241
242  idx = tree.indexOfAttribute(xml::kSchemaAndroid, "id");
243  ASSERT_THAT(idx, Ge(0));
244  EXPECT_THAT(tree.indexOfID(), Eq(idx));
245  EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x010100d0u));
246
247  idx = tree.indexOfAttribute(xml::kSchemaAuto, "foo");
248  ASSERT_THAT(idx, Ge(0));
249  EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x80010000u));
250  EXPECT_THAT(tree.getAttributeDataType(idx), Eq(android::Res_value::TYPE_REFERENCE));
251  EXPECT_THAT(tree.getAttributeData(idx), Eq(int32_t(0x80020000)));
252}
253
254TEST_F(XmlFlattenerTest, ProcessEscapedStrings) {
255  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(
256      R"(<element value="\?hello" pattern="\\d{5}" other="&quot;">\\d{5}</element>)");
257
258  android::ResXMLTree tree;
259  ASSERT_TRUE(Flatten(doc.get(), &tree));
260
261  while (tree.next() != android::ResXMLTree::START_TAG) {
262    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
263    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
264  }
265
266  const StringPiece16 kValue = u"value";
267  const StringPiece16 kPattern = u"pattern";
268  const StringPiece16 kOther = u"other";
269
270  size_t len;
271  ssize_t idx;
272
273  idx = tree.indexOfAttribute(nullptr, 0, kValue.data(), kValue.size());
274  ASSERT_THAT(idx, Ge(0));
275  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"?hello"));
276
277  idx = tree.indexOfAttribute(nullptr, 0, kPattern.data(), kPattern.size());
278  ASSERT_THAT(idx, Ge(0));
279  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\\d{5}"));
280
281  idx = tree.indexOfAttribute(nullptr, 0, kOther.data(), kOther.size());
282  ASSERT_THAT(idx, Ge(0));
283  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\""));
284
285  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT));
286  EXPECT_THAT(tree.getText(&len), StrEq(u"\\d{5}"));
287}
288
289TEST_F(XmlFlattenerTest, FlattenRawValueOnlyMakesCompiledValueToo) {
290  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element foo="bar" />)");
291
292  // Raw values are kept when encoding an attribute with no compiled value, regardless of option.
293  XmlFlattenerOptions options;
294  options.keep_raw_values = false;
295
296  android::ResXMLTree tree;
297  ASSERT_TRUE(Flatten(doc.get(), &tree, options));
298
299  while (tree.next() != android::ResXMLTree::START_TAG) {
300    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
301    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
302  }
303
304  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
305  EXPECT_THAT(tree.getAttributeValueStringID(0), Ge(0));
306  EXPECT_THAT(tree.getAttributeDataType(0), Eq(android::Res_value::TYPE_STRING));
307  EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(tree.getAttributeData(0)));
308}
309
310TEST_F(XmlFlattenerTest, FlattenCompiledStringValuePreservesRawValue) {
311  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element foo="bar" />)");
312  doc->root->attributes[0].compiled_value =
313      util::make_unique<String>(doc->string_pool.MakeRef("bar"));
314
315  // Raw values are kept when encoding a string anyways.
316  XmlFlattenerOptions options;
317  options.keep_raw_values = false;
318
319  android::ResXMLTree tree;
320  ASSERT_TRUE(Flatten(doc.get(), &tree, options));
321
322  while (tree.next() != android::ResXMLTree::START_TAG) {
323    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
324    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
325  }
326
327  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
328  EXPECT_THAT(tree.getAttributeValueStringID(0), Ge(0));
329  EXPECT_THAT(tree.getAttributeDataType(0), Eq(android::Res_value::TYPE_STRING));
330  EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(tree.getAttributeData(0)));
331}
332
333TEST_F(XmlFlattenerTest, FlattenCompiledValueExcludesRawValueWithKeepRawOptionFalse) {
334  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element foo="true" />)");
335  doc->root->attributes[0].compiled_value = ResourceUtils::MakeBool(true);
336
337  XmlFlattenerOptions options;
338  options.keep_raw_values = false;
339
340  android::ResXMLTree tree;
341  ASSERT_TRUE(Flatten(doc.get(), &tree, options));
342
343  while (tree.next() != android::ResXMLTree::START_TAG) {
344    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
345    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
346  }
347
348  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
349  EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1));
350  EXPECT_THAT(tree.getAttributeDataType(0), Eq(android::Res_value::TYPE_INT_BOOLEAN));
351}
352
353TEST_F(XmlFlattenerTest, FlattenCompiledValueExcludesRawValueWithKeepRawOptionTrue) {
354  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element foo="true" />)");
355  doc->root->attributes[0].compiled_value = ResourceUtils::MakeBool(true);
356
357  XmlFlattenerOptions options;
358  options.keep_raw_values = true;
359
360  android::ResXMLTree tree;
361  ASSERT_TRUE(Flatten(doc.get(), &tree, options));
362
363  while (tree.next() != android::ResXMLTree::START_TAG) {
364    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
365    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
366  }
367
368  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
369  EXPECT_THAT(tree.getAttributeValueStringID(0), Ge(0));
370
371  size_t len;
372  EXPECT_THAT(tree.getAttributeStringValue(0, &len), StrEq(u"true"));
373
374  EXPECT_THAT(tree.getAttributeDataType(0), Eq(android::Res_value::TYPE_INT_BOOLEAN));
375}
376
377}  // namespace aapt
378