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