blob: dd6f63ad3f653917b27e1052d6c2ff8a9ff0ea84 [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
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 "BigBuffer.h"
18#include "Logger.h"
19#include "Maybe.h"
20#include "Resolver.h"
21#include "Resource.h"
22#include "ResourceParser.h"
23#include "ResourceValues.h"
24#include "SdkConstants.h"
25#include "Source.h"
26#include "StringPool.h"
27#include "Util.h"
28#include "XmlFlattener.h"
29
30#include <androidfw/ResourceTypes.h>
31#include <limits>
32#include <map>
33#include <string>
34#include <vector>
35
36namespace aapt {
37
Adam Lesinski769de982015-04-10 19:43:55 -070038constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
39constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
40constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/";
41
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080042struct AttributeValueFlattener : ValueVisitor {
43 struct Args : ValueVisitorArgs {
44 Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV,
45 std::shared_ptr<XmlPullParser> p, bool& e, StringPool::Ref& rV,
46 std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& sR) :
47 resolver(r), logger(s), outValue(oV), parser(p), error(e), rawValue(rV),
48 stringRefs(sR) {
49 }
50
51 std::shared_ptr<Resolver> resolver;
52 SourceLogger& logger;
53 android::Res_value& outValue;
54 std::shared_ptr<XmlPullParser> parser;
55 bool& error;
56 StringPool::Ref& rawValue;
57 std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& stringRefs;
58 };
59
60 void visit(Reference& reference, ValueVisitorArgs& a) override {
61 Args& args = static_cast<Args&>(a);
62
63 Maybe<ResourceId> result = args.resolver->findId(reference.name);
64 if (!result || !result.value().isValid()) {
65 args.logger.error(args.parser->getLineNumber())
66 << "unresolved reference '"
67 << reference.name
68 << "'."
69 << std::endl;
70 args.error = true;
71 } else {
72 reference.id = result.value();
73 reference.flatten(args.outValue);
74 }
75 }
76
77 void visit(String& string, ValueVisitorArgs& a) override {
78 Args& args = static_cast<Args&>(a);
79
80 args.outValue.dataType = android::Res_value::TYPE_STRING;
81 args.stringRefs.emplace_back(args.rawValue,
82 reinterpret_cast<android::ResStringPool_ref*>(&args.outValue.data));
83 }
84
85 void visitItem(Item& item, ValueVisitorArgs& a) override {
86 Args& args = static_cast<Args&>(a);
87 item.flatten(args.outValue);
88 }
89};
90
91struct XmlAttribute {
92 uint32_t resourceId;
93 const XmlPullParser::Attribute* xmlAttr;
94 const Attribute* attr;
95 StringPool::Ref nameRef;
96};
97
98static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
99 return a.resourceId < id;
100}
101
Adam Lesinski769de982015-04-10 19:43:55 -0700102XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table,
103 const std::shared_ptr<Resolver>& resolver) :
104 mTable(table), mResolver(resolver) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800105}
106
107/**
108 * Reads events from the parser and writes to a BigBuffer. The binary XML file
109 * expects the StringPool to appear first, but we haven't collected the strings yet. We
110 * write to a temporary BigBuffer while parsing the input, adding strings we encounter
111 * to the StringPool. At the end, we write the StringPool to the given BigBuffer and
112 * then move the data from the temporary BigBuffer into the given one. This incurs no
113 * copies as the given BigBuffer simply takes ownership of the data.
114 */
115Maybe<size_t> XmlFlattener::flatten(const Source& source,
116 const std::shared_ptr<XmlPullParser>& parser,
117 BigBuffer* outBuffer, Options options) {
118 SourceLogger logger(source);
119 StringPool pool;
120 bool error = false;
121
122 size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max();
123
124 // Attribute names are stored without packages, but we use
125 // their StringPool index to lookup their resource IDs.
126 // This will cause collisions, so we can't dedupe
127 // attribute names from different packages. We use separate
128 // pools that we later combine.
129 std::map<std::u16string, StringPool> packagePools;
130
131 // Attribute resource IDs are stored in the same order
132 // as the attribute names appear in the StringPool.
133 // Since the StringPool contains more than just attribute
134 // names, to maintain a tight packing of resource IDs,
135 // we must ensure that attribute names appear first
136 // in our StringPool. For this, we assign a low priority
137 // (0xffffffff) to non-attribute strings. Attribute
138 // names will be stored along with a priority equal
139 // to their resource ID so that they are ordered.
140 StringPool::Context lowPriority { 0xffffffffu };
141
142 // Once we sort the StringPool, we can assign the updated indices
143 // to the correct data locations.
144 std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> stringRefs;
145
146 // Since we don't know the size of the final StringPool, we write to this
147 // temporary BigBuffer, which we will append to outBuffer later.
148 BigBuffer out(1024);
149 while (XmlPullParser::isGoodEvent(parser->next())) {
150 XmlPullParser::Event event = parser->getEvent();
151 switch (event) {
152 case XmlPullParser::Event::kStartNamespace:
153 case XmlPullParser::Event::kEndNamespace: {
154 const size_t startIndex = out.size();
155 android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
156 if (event == XmlPullParser::Event::kStartNamespace) {
157 node->header.type = android::RES_XML_START_NAMESPACE_TYPE;
158 } else {
159 node->header.type = android::RES_XML_END_NAMESPACE_TYPE;
160 }
161
162 node->header.headerSize = sizeof(*node);
163 node->lineNumber = parser->getLineNumber();
164 node->comment.index = -1;
165
166 android::ResXMLTree_namespaceExt* ns =
167 out.nextBlock<android::ResXMLTree_namespaceExt>();
168 stringRefs.emplace_back(
169 pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix);
170 stringRefs.emplace_back(
171 pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri);
172
173 out.align4();
174 node->header.size = out.size() - startIndex;
175 break;
176 }
177
178 case XmlPullParser::Event::kStartElement: {
179 const size_t startIndex = out.size();
180 android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
181 node->header.type = android::RES_XML_START_ELEMENT_TYPE;
182 node->header.headerSize = sizeof(*node);
183 node->lineNumber = parser->getLineNumber();
184 node->comment.index = -1;
185
186 android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>();
187 stringRefs.emplace_back(
188 pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
189 stringRefs.emplace_back(
190 pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
191 elem->attributeStart = sizeof(*elem);
192 elem->attributeSize = sizeof(android::ResXMLTree_attribute);
193
194 // The resource system expects attributes to be sorted by resource ID.
195 std::vector<XmlAttribute> sortedAttributes;
196 uint32_t nextAttributeId = 0;
197 const auto endAttrIter = parser->endAttributes();
198 for (auto attrIter = parser->beginAttributes();
Adam Lesinski769de982015-04-10 19:43:55 -0700199 attrIter != endAttrIter;
200 ++attrIter) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800201 uint32_t id;
202 StringPool::Ref nameRef;
203 const Attribute* attr = nullptr;
Adam Lesinski769de982015-04-10 19:43:55 -0700204
205 if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) {
206 size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
207 if (sdkVersion > options.maxSdkAttribute.value()) {
208 // We will silently omit this attribute
209 smallestStrippedAttributeSdk =
210 std::min(smallestStrippedAttributeSdk, sdkVersion);
211 continue;
212 }
213 }
214
215 ResourceNameRef genIdName;
216 bool create = false;
217 bool privateRef = false;
218 if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName,
219 &create, &privateRef) && create) {
220 mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()),
221 util::make_unique<Id>());
222 }
223
224
225 StringPiece16 package;
226 if (util::stringStartsWith<char16_t>(attrIter->namespaceUri, kSchemaPrefix)) {
227 StringPiece16 schemaPrefix = kSchemaPrefix;
228 package = attrIter->namespaceUri;
229 package = package.substr(schemaPrefix.size(),
230 package.size() - schemaPrefix.size());
231 } else if (attrIter->namespaceUri == kSchemaAuto && mResolver) {
232 package = mResolver->getDefaultPackage();
233 }
234
235 if (package.empty() || !mResolver) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800236 // Attributes that have no resource ID (because they don't belong to a
237 // package) should appear after those that do have resource IDs. Assign
238 // them some/ integer value that will appear after.
239 id = 0x80000000u | nextAttributeId++;
240 nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800241
Adam Lesinski769de982015-04-10 19:43:55 -0700242 } else {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800243 // Find the Attribute object via our Resolver.
244 ResourceName attrName = {
245 package.toString(), ResourceType::kAttr, attrIter->name };
246 Maybe<Resolver::Entry> result = mResolver->findAttribute(attrName);
247 if (!result || !result.value().id.isValid()) {
248 logger.error(parser->getLineNumber())
249 << "unresolved attribute '"
250 << attrName
251 << "'."
252 << std::endl;
253 error = true;
254 continue;
255 }
256
257 if (!result.value().attr) {
258 logger.error(parser->getLineNumber())
259 << "not a valid attribute '"
260 << attrName
261 << "'."
262 << std::endl;
263 error = true;
264 continue;
265 }
266
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800267 id = result.value().id.id;
268 attr = result.value().attr;
269
270 // Put the attribute name into a package specific pool, since we don't
271 // want to collapse names from different packages.
272 nameRef = packagePools[package.toString()].makeRef(
273 attrIter->name, StringPool::Context{ id });
274 }
275
276 // Insert the attribute into the sorted vector.
277 auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
278 id, lessAttributeId);
279 sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef });
280 }
281
282 if (error) {
283 break;
284 }
285
286 // Now that we have filtered out some attributes, get the final count.
287 elem->attributeCount = sortedAttributes.size();
288
289 // Flatten the sorted attributes.
290 for (auto entry : sortedAttributes) {
291 android::ResXMLTree_attribute* attr =
292 out.nextBlock<android::ResXMLTree_attribute>();
293 stringRefs.emplace_back(
294 pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns);
295 StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
296 stringRefs.emplace_back(rawValueRef, &attr->rawValue);
297 stringRefs.emplace_back(entry.nameRef, &attr->name);
298
299 if (entry.attr) {
300 std::unique_ptr<Item> value = ResourceParser::parseItemForAttribute(
301 entry.xmlAttr->value, *entry.attr, mResolver->getDefaultPackage());
302 if (value) {
303 AttributeValueFlattener flattener;
304 value->accept(flattener, AttributeValueFlattener::Args{
305 mResolver,
306 logger,
307 attr->typedValue,
308 parser,
309 error,
310 rawValueRef,
311 stringRefs
312 });
313 } else if (!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
314 logger.error(parser->getLineNumber())
315 << "'"
316 << *rawValueRef
317 << "' is not compatible with attribute "
318 << *entry.attr
319 << "."
320 << std::endl;
321 error = true;
322 } else {
323 attr->typedValue.dataType = android::Res_value::TYPE_STRING;
324 stringRefs.emplace_back(rawValueRef,
325 reinterpret_cast<android::ResStringPool_ref*>(
326 &attr->typedValue.data));
327 }
328 } else {
329 attr->typedValue.dataType = android::Res_value::TYPE_STRING;
330 stringRefs.emplace_back(rawValueRef,
331 reinterpret_cast<android::ResStringPool_ref*>(
332 &attr->typedValue.data));
333 }
334 attr->typedValue.size = sizeof(attr->typedValue);
335 }
336
337 out.align4();
338 node->header.size = out.size() - startIndex;
339 break;
340 }
341
342 case XmlPullParser::Event::kEndElement: {
343 const size_t startIndex = out.size();
344 android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
345 node->header.type = android::RES_XML_END_ELEMENT_TYPE;
346 node->header.headerSize = sizeof(*node);
347 node->lineNumber = parser->getLineNumber();
348 node->comment.index = -1;
349
350 android::ResXMLTree_endElementExt* elem =
351 out.nextBlock<android::ResXMLTree_endElementExt>();
352 stringRefs.emplace_back(
353 pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
354 stringRefs.emplace_back(
355 pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
356
357 out.align4();
358 node->header.size = out.size() - startIndex;
359 break;
360 }
361
362 case XmlPullParser::Event::kText: {
363 StringPiece16 text = util::trimWhitespace(parser->getText());
364 if (text.empty()) {
365 break;
366 }
367
368 const size_t startIndex = out.size();
369 android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
370 node->header.type = android::RES_XML_CDATA_TYPE;
371 node->header.headerSize = sizeof(*node);
372 node->lineNumber = parser->getLineNumber();
373 node->comment.index = -1;
374
375 android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>();
376 stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data);
377
378 out.align4();
379 node->header.size = out.size() - startIndex;
380 break;
381 }
382
383 default:
384 break;
385 }
386
387 }
388 out.align4();
389
390 if (error) {
391 return {};
392 }
393
394 if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
395 logger.error(parser->getLineNumber())
396 << parser->getLastError()
397 << std::endl;
398 return {};
399 }
400
401 // Merge the package pools into the main pool.
402 for (auto& packagePoolEntry : packagePools) {
403 pool.merge(std::move(packagePoolEntry.second));
404 }
405
406 // Sort so that attribute resource IDs show up first.
407 pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
408 return a.context.priority < b.context.priority;
409 });
410
411 // Now we flatten the string pool references into the correct places.
412 for (const auto& refEntry : stringRefs) {
413 refEntry.second->index = refEntry.first.getIndex();
414 }
415
416 // Write the XML header.
417 const size_t beforeXmlTreeIndex = outBuffer->size();
418 android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
419 header->header.type = android::RES_XML_TYPE;
420 header->header.headerSize = sizeof(*header);
421
422 // Write the array of resource IDs, indexed by StringPool order.
423 const size_t beforeResIdMapIndex = outBuffer->size();
424 android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
425 resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
426 resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
427 for (const auto& str : pool) {
428 ResourceId id { str->context.priority };
429 if (!id.isValid()) {
430 // When we see the first non-resource ID,
431 // we're done.
432 break;
433 }
434
435 uint32_t* flatId = outBuffer->nextBlock<uint32_t>();
436 *flatId = id.id;
437 }
438 resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
439
440 // Flatten the StringPool.
441 StringPool::flattenUtf8(outBuffer, pool);
442
443 // Move the temporary BigBuffer into outBuffer->
444 outBuffer->appendBuffer(std::move(out));
445
446 header->header.size = outBuffer->size() - beforeXmlTreeIndex;
447
448 if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) {
449 // Nothing was stripped
450 return 0u;
451 }
452 return smallestStrippedAttributeSdk;
453}
454
455} // namespace aapt