blob: 94688606391a563a259902dbedda74012fd33c5b [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 "ConfigDescription.h"
18#include "Logger.h"
Adam Lesinski769de982015-04-10 19:43:55 -070019#include "NameMangler.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080020#include "ResourceTable.h"
21#include "ResourceValues.h"
22#include "Util.h"
23
24#include <algorithm>
25#include <androidfw/ResourceTypes.h>
26#include <memory>
27#include <string>
28#include <tuple>
29
30namespace aapt {
31
32static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
33 return lhs.config < rhs;
34}
35
36static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
37 return lhs->type < rhs;
38}
39
40static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) {
41 return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
42}
43
44ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
45}
46
47std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) {
48 auto last = mTypes.end();
49 auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType);
50 if (iter != last) {
51 if ((*iter)->type == type) {
52 return *iter;
53 }
54 }
55 return *mTypes.emplace(iter, new ResourceTableType{ type });
56}
57
58std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry(
59 std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) {
60 auto last = type->entries.end();
61 auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry);
62 if (iter != last) {
63 if (name == (*iter)->name) {
64 return *iter;
65 }
66 }
67 return *type->entries.emplace(iter, new ResourceEntry{ name });
68}
69
70struct IsAttributeVisitor : ConstValueVisitor {
71 bool isAttribute = false;
72
73 void visit(const Attribute&, ValueVisitorArgs&) override {
74 isAttribute = true;
75 }
76
77 operator bool() {
78 return isAttribute;
79 }
80};
81
82/**
83 * The default handler for collisions. A return value of -1 means keep the
84 * existing value, 0 means fail, and +1 means take the incoming value.
85 */
86static int defaultCollisionHandler(const Value& existing, const Value& incoming) {
87 IsAttributeVisitor existingIsAttr, incomingIsAttr;
88 existing.accept(existingIsAttr, {});
89 incoming.accept(incomingIsAttr, {});
90
91 if (!incomingIsAttr) {
92 if (incoming.isWeak()) {
93 // We're trying to add a weak resource but a resource
94 // already exists. Keep the existing.
95 return -1;
96 } else if (existing.isWeak()) {
97 // Override the weak resource with the new strong resource.
98 return 1;
99 }
100 // The existing and incoming values are strong, this is an error
101 // if the values are not both attributes.
102 return 0;
103 }
104
105 if (!existingIsAttr) {
106 if (existing.isWeak()) {
107 // The existing value is not an attribute and it is weak,
108 // so take the incoming attribute value.
109 return 1;
110 }
111 // The existing value is not an attribute and it is strong,
112 // so the incoming attribute value is an error.
113 return 0;
114 }
115
116 //
117 // Attribute specific handling. At this point we know both
118 // values are attributes. Since we can declare and define
119 // attributes all-over, we do special handling to see
120 // which definition sticks.
121 //
122 const Attribute& existingAttr = static_cast<const Attribute&>(existing);
123 const Attribute& incomingAttr = static_cast<const Attribute&>(incoming);
124 if (existingAttr.typeMask == incomingAttr.typeMask) {
125 // The two attributes are both DECLs, but they are plain attributes
126 // with the same formats.
127 // Keep the strongest one.
128 return existingAttr.isWeak() ? 1 : -1;
129 }
130
131 if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
132 // Any incoming attribute is better than this.
133 return 1;
134 }
135
136 if (incomingAttr.isWeak() && incomingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
137 // The incoming attribute may be a USE instead of a DECL.
138 // Keep the existing attribute.
139 return -1;
140 }
141 return 0;
142}
143
144static constexpr const char16_t* kValidNameChars = u"._-";
145
146bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
147 const ConfigDescription& config, const SourceLine& source,
148 std::unique_ptr<Value> value) {
149 if (!name.package.empty() && name.package != mPackage) {
150 Logger::error(source)
151 << "resource '"
152 << name
153 << "' has incompatible package. Must be '"
154 << mPackage
155 << "'."
156 << std::endl;
157 return false;
158 }
159
160 auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars);
161 if (badCharIter != name.entry.end()) {
162 Logger::error(source)
163 << "resource '"
164 << name
165 << "' has invalid entry name '"
166 << name.entry
167 << "'. Invalid character '"
Adam Lesinskica2fc352015-04-03 12:08:26 -0700168 << StringPiece16(badCharIter, 1)
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800169 << "'."
170 << std::endl;
171 return false;
172 }
173
174 std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
175 if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
176 type->typeId != resId.typeId()) {
177 Logger::error(source)
178 << "trying to add resource '"
179 << name
180 << "' with ID "
181 << resId
182 << " but type '"
183 << type->type
184 << "' already has ID "
185 << std::hex << type->typeId << std::dec
186 << "."
187 << std::endl;
188 return false;
189 }
190
191 std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
192 if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
193 entry->entryId != resId.entryId()) {
194 Logger::error(source)
195 << "trying to add resource '"
196 << name
197 << "' with ID "
198 << resId
199 << " but resource already has ID "
200 << ResourceId(mPackageId, type->typeId, entry->entryId)
201 << "."
202 << std::endl;
203 return false;
204 }
205
206 const auto endIter = std::end(entry->values);
207 auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs);
208 if (iter == endIter || iter->config != config) {
209 // This resource did not exist before, add it.
210 entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
211 } else {
212 int collisionResult = defaultCollisionHandler(*iter->value, *value);
213 if (collisionResult > 0) {
214 // Take the incoming value.
215 *iter = ResourceConfigValue{ config, source, {}, std::move(value) };
216 } else if (collisionResult == 0) {
217 Logger::error(source)
218 << "duplicate value for resource '" << name << "' "
219 << "with config '" << iter->config << "'."
220 << std::endl;
221
222 Logger::error(iter->source)
223 << "resource previously defined here."
224 << std::endl;
225 return false;
226 }
227 }
228
229 if (resId.isValid()) {
230 type->typeId = resId.typeId();
231 entry->entryId = resId.entryId();
232 }
233 return true;
234}
235
236bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
237 const SourceLine& source, std::unique_ptr<Value> value) {
238 return addResource(name, ResourceId{}, config, source, std::move(value));
239}
240
241bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
242 const SourceLine& source) {
243 if (!name.package.empty() && name.package != mPackage) {
244 Logger::error(source)
245 << "resource '"
246 << name
247 << "' has incompatible package. Must be '"
248 << mPackage
249 << "'."
250 << std::endl;
251 return false;
252 }
253
254 auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars);
255 if (badCharIter != name.entry.end()) {
256 Logger::error(source)
257 << "resource '"
258 << name
259 << "' has invalid entry name '"
260 << name.entry
261 << "'. Invalid character '"
Adam Lesinskica2fc352015-04-03 12:08:26 -0700262 << StringPiece16(badCharIter, 1)
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800263 << "'."
264 << std::endl;
265 return false;
266 }
267
268 std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
269 if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
270 type->typeId != resId.typeId()) {
271 Logger::error(source)
272 << "trying to make resource '"
273 << name
274 << "' public with ID "
275 << resId
276 << " but type '"
277 << type->type
278 << "' already has ID "
279 << std::hex << type->typeId << std::dec
280 << "."
281 << std::endl;
282 return false;
283 }
284
285 std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
286 if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
287 entry->entryId != resId.entryId()) {
288 Logger::error(source)
289 << "trying to make resource '"
290 << name
291 << "' public with ID "
292 << resId
293 << " but resource already has ID "
294 << ResourceId(mPackageId, type->typeId, entry->entryId)
295 << "."
296 << std::endl;
297 return false;
298 }
299
300 type->publicStatus.isPublic = true;
301 entry->publicStatus.isPublic = true;
Adam Lesinski6ff19662015-04-30 17:40:46 -0700302 entry->publicStatus.source = source;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800303
304 if (resId.isValid()) {
305 type->typeId = resId.typeId();
306 entry->entryId = resId.entryId();
307 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800308 return true;
309}
310
Adam Lesinski769de982015-04-10 19:43:55 -0700311bool ResourceTable::merge(ResourceTable&& other) {
312 const bool mangleNames = mPackage != other.getPackage();
313 std::u16string mangledName;
314
315 for (auto& otherType : other) {
316 std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type);
Adam Lesinski24aad162015-04-24 19:19:30 -0700317 if (otherType->publicStatus.isPublic) {
318 if (type->publicStatus.isPublic && type->typeId != otherType->typeId) {
319 Logger::error() << "can not merge type '" << type->type
320 << "': conflicting public IDs "
321 << "(" << type->typeId << " vs " << otherType->typeId << ")."
322 << std::endl;
323 return false;
324 }
325 type->publicStatus = std::move(otherType->publicStatus);
326 type->typeId = otherType->typeId;
Adam Lesinski769de982015-04-10 19:43:55 -0700327 }
328
329 for (auto& otherEntry : otherType->entries) {
330 const std::u16string* nameToAdd = &otherEntry->name;
331 if (mangleNames) {
332 mangledName = otherEntry->name;
333 NameMangler::mangle(other.getPackage(), &mangledName);
334 nameToAdd = &mangledName;
335 }
336
337 std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd);
Adam Lesinski24aad162015-04-24 19:19:30 -0700338 if (otherEntry->publicStatus.isPublic) {
339 if (entry->publicStatus.isPublic && entry->entryId != otherEntry->entryId) {
340 Logger::error() << "can not merge entry '" << type->type << "/" << entry->name
341 << "': conflicting public IDs "
342 << "(" << entry->entryId << " vs " << entry->entryId << ")."
343 << std::endl;
344 return false;
345 }
346 entry->publicStatus = std::move(otherEntry->publicStatus);
347 entry->entryId = otherEntry->entryId;
Adam Lesinski769de982015-04-10 19:43:55 -0700348 }
349
350 for (ResourceConfigValue& otherValue : otherEntry->values) {
351 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
352 otherValue.config, compareConfigs);
353 if (iter != entry->values.end() && iter->config == otherValue.config) {
354 int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value);
355 if (collisionResult > 0) {
356 // Take the incoming value.
357 iter->source = std::move(otherValue.source);
358 iter->comment = std::move(otherValue.comment);
359 iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool));
360 } else if (collisionResult == 0) {
361 ResourceNameRef resourceName = { mPackage, type->type, entry->name };
362 Logger::error(otherValue.source)
363 << "resource '" << resourceName << "' has a conflicting value for "
364 << "configuration (" << otherValue.config << ")."
365 << std::endl;
366 Logger::note(iter->source) << "originally defined here." << std::endl;
367 return false;
368 }
369 } else {
370 entry->values.insert(iter, ResourceConfigValue{
371 otherValue.config,
372 std::move(otherValue.source),
373 std::move(otherValue.comment),
374 std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)),
375 });
376 }
377 }
378 }
379 }
380 return true;
381}
382
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800383std::tuple<const ResourceTableType*, const ResourceEntry*>
384ResourceTable::findResource(const ResourceNameRef& name) const {
385 if (name.package != mPackage) {
Adam Lesinskica2fc352015-04-03 12:08:26 -0700386 return {};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800387 }
388
389 auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
390 if (iter == mTypes.end() || (*iter)->type != name.type) {
Adam Lesinskica2fc352015-04-03 12:08:26 -0700391 return {};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800392 }
393
394 const std::unique_ptr<ResourceTableType>& type = *iter;
395 auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry,
396 lessThanEntry);
397 if (iter2 == type->entries.end() || name.entry != (*iter2)->name) {
Adam Lesinskica2fc352015-04-03 12:08:26 -0700398 return {};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800399 }
Adam Lesinskica2fc352015-04-03 12:08:26 -0700400 return std::make_tuple(iter->get(), iter2->get());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800401}
402
403} // namespace aapt