ResourceTable.cpp revision 6ff19664f9279023c96e5a65c3059e1ef4beac0f
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 "ConfigDescription.h"
18#include "Logger.h"
19#include "NameMangler.h"
20#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 '"
168                << StringPiece16(badCharIter, 1)
169                << "'."
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 '"
262                << StringPiece16(badCharIter, 1)
263                << "'."
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;
302    entry->publicStatus.source = source;
303
304    if (resId.isValid()) {
305        type->typeId = resId.typeId();
306        entry->entryId = resId.entryId();
307    }
308    return true;
309}
310
311bool 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);
317        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;
327        }
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);
338            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;
348            }
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
383std::tuple<const ResourceTableType*, const ResourceEntry*>
384ResourceTable::findResource(const ResourceNameRef& name) const {
385    if (name.package != mPackage) {
386        return {};
387    }
388
389    auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
390    if (iter == mTypes.end() || (*iter)->type != name.type) {
391        return {};
392    }
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) {
398        return {};
399    }
400    return std::make_tuple(iter->get(), iter2->get());
401}
402
403} // namespace aapt
404