ResourceTable.cpp revision 6f6ceb7e1456698b1f33e04536bfb3227f9fcfcb
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 "ResourceTable.h"
20#include "ResourceValues.h"
21#include "Util.h"
22
23#include <algorithm>
24#include <androidfw/ResourceTypes.h>
25#include <memory>
26#include <string>
27#include <tuple>
28
29namespace aapt {
30
31static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
32    return lhs.config < rhs;
33}
34
35static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
36    return lhs->type < rhs;
37}
38
39static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) {
40    return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
41}
42
43ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
44}
45
46std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) {
47    auto last = mTypes.end();
48    auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType);
49    if (iter != last) {
50        if ((*iter)->type == type) {
51            return *iter;
52        }
53    }
54    return *mTypes.emplace(iter, new ResourceTableType{ type });
55}
56
57std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry(
58        std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) {
59    auto last = type->entries.end();
60    auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry);
61    if (iter != last) {
62        if (name == (*iter)->name) {
63            return *iter;
64        }
65    }
66    return *type->entries.emplace(iter, new ResourceEntry{ name });
67}
68
69struct IsAttributeVisitor : ConstValueVisitor {
70    bool isAttribute = false;
71
72    void visit(const Attribute&, ValueVisitorArgs&) override {
73        isAttribute = true;
74    }
75
76    operator bool() {
77        return isAttribute;
78    }
79};
80
81/**
82 * The default handler for collisions. A return value of -1 means keep the
83 * existing value, 0 means fail, and +1 means take the incoming value.
84 */
85static int defaultCollisionHandler(const Value& existing, const Value& incoming) {
86    IsAttributeVisitor existingIsAttr, incomingIsAttr;
87    existing.accept(existingIsAttr, {});
88    incoming.accept(incomingIsAttr, {});
89
90    if (!incomingIsAttr) {
91        if (incoming.isWeak()) {
92            // We're trying to add a weak resource but a resource
93            // already exists. Keep the existing.
94            return -1;
95        } else if (existing.isWeak()) {
96            // Override the weak resource with the new strong resource.
97            return 1;
98        }
99        // The existing and incoming values are strong, this is an error
100        // if the values are not both attributes.
101        return 0;
102    }
103
104    if (!existingIsAttr) {
105        if (existing.isWeak()) {
106            // The existing value is not an attribute and it is weak,
107            // so take the incoming attribute value.
108            return 1;
109        }
110        // The existing value is not an attribute and it is strong,
111        // so the incoming attribute value is an error.
112        return 0;
113    }
114
115    //
116    // Attribute specific handling. At this point we know both
117    // values are attributes. Since we can declare and define
118    // attributes all-over, we do special handling to see
119    // which definition sticks.
120    //
121    const Attribute& existingAttr = static_cast<const Attribute&>(existing);
122    const Attribute& incomingAttr = static_cast<const Attribute&>(incoming);
123    if (existingAttr.typeMask == incomingAttr.typeMask) {
124        // The two attributes are both DECLs, but they are plain attributes
125        // with the same formats.
126        // Keep the strongest one.
127        return existingAttr.isWeak() ? 1 : -1;
128    }
129
130    if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
131        // Any incoming attribute is better than this.
132        return 1;
133    }
134
135    if (incomingAttr.isWeak() && incomingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
136        // The incoming attribute may be a USE instead of a DECL.
137        // Keep the existing attribute.
138        return -1;
139    }
140    return 0;
141}
142
143static constexpr const char16_t* kValidNameChars = u"._-";
144
145bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
146        const ConfigDescription& config, const SourceLine& source,
147        std::unique_ptr<Value> value) {
148    if (!name.package.empty() && name.package != mPackage) {
149        Logger::error(source)
150                << "resource '"
151                << name
152                << "' has incompatible package. Must be '"
153                << mPackage
154                << "'."
155                << std::endl;
156        return false;
157    }
158
159    auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars);
160    if (badCharIter != name.entry.end()) {
161        Logger::error(source)
162                << "resource '"
163                << name
164                << "' has invalid entry name '"
165                << name.entry
166                << "'. Invalid character '"
167                << *badCharIter
168                << "'."
169                << std::endl;
170        return false;
171    }
172
173    std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
174    if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
175            type->typeId != resId.typeId()) {
176        Logger::error(source)
177                << "trying to add resource '"
178                << name
179                << "' with ID "
180                << resId
181                << " but type '"
182                << type->type
183                << "' already has ID "
184                << std::hex << type->typeId << std::dec
185                << "."
186                << std::endl;
187        return false;
188    }
189
190    std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
191    if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
192            entry->entryId != resId.entryId()) {
193        Logger::error(source)
194                << "trying to add resource '"
195                << name
196                << "' with ID "
197                << resId
198                << " but resource already has ID "
199                << ResourceId(mPackageId, type->typeId, entry->entryId)
200                << "."
201                << std::endl;
202        return false;
203    }
204
205    const auto endIter = std::end(entry->values);
206    auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs);
207    if (iter == endIter || iter->config != config) {
208        // This resource did not exist before, add it.
209        entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
210    } else {
211        int collisionResult = defaultCollisionHandler(*iter->value, *value);
212        if (collisionResult > 0) {
213            // Take the incoming value.
214            *iter = ResourceConfigValue{ config, source, {}, std::move(value) };
215        } else if (collisionResult == 0) {
216            Logger::error(source)
217                    << "duplicate value for resource '" << name << "' "
218                    << "with config '" << iter->config << "'."
219                    << std::endl;
220
221            Logger::error(iter->source)
222                    << "resource previously defined here."
223                    << std::endl;
224            return false;
225        }
226    }
227
228    if (resId.isValid()) {
229        type->typeId = resId.typeId();
230        entry->entryId = resId.entryId();
231    }
232    return true;
233}
234
235bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
236                                const SourceLine& source, std::unique_ptr<Value> value) {
237    return addResource(name, ResourceId{}, config, source, std::move(value));
238}
239
240bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
241                               const SourceLine& source) {
242    if (!name.package.empty() && name.package != mPackage) {
243        Logger::error(source)
244                << "resource '"
245                << name
246                << "' has incompatible package. Must be '"
247                << mPackage
248                << "'."
249            << std::endl;
250        return false;
251    }
252
253    auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars);
254    if (badCharIter != name.entry.end()) {
255        Logger::error(source)
256                << "resource '"
257                << name
258                << "' has invalid entry name '"
259                << name.entry
260                << "'. Invalid character '"
261                << *badCharIter
262                << "'."
263                << std::endl;
264        return false;
265    }
266
267    std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
268    if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
269            type->typeId != resId.typeId()) {
270        Logger::error(source)
271                << "trying to make resource '"
272                << name
273                << "' public with ID "
274                << resId
275                << " but type '"
276                << type->type
277                << "' already has ID "
278                << std::hex << type->typeId << std::dec
279                << "."
280                << std::endl;
281        return false;
282    }
283
284    std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
285    if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
286            entry->entryId != resId.entryId()) {
287        Logger::error(source)
288                << "trying to make resource '"
289                << name
290                << "' public with ID "
291                << resId
292                << " but resource already has ID "
293                << ResourceId(mPackageId, type->typeId, entry->entryId)
294                << "."
295                << std::endl;
296        return false;
297    }
298
299    type->publicStatus.isPublic = true;
300    entry->publicStatus.isPublic = true;
301
302    if (resId.isValid()) {
303        type->typeId = resId.typeId();
304        entry->entryId = resId.entryId();
305    }
306
307    if (entry->values.empty()) {
308        entry->values.push_back(ResourceConfigValue{ {}, source, {},
309                                    util::make_unique<Sentinel>() });
310    }
311    return true;
312}
313
314std::tuple<const ResourceTableType*, const ResourceEntry*>
315ResourceTable::findResource(const ResourceNameRef& name) const {
316    if (name.package != mPackage) {
317        return {nullptr, nullptr};
318    }
319
320    auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
321    if (iter == mTypes.end() || (*iter)->type != name.type) {
322        return {nullptr, nullptr};
323    }
324
325    const std::unique_ptr<ResourceTableType>& type = *iter;
326    auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry,
327                                  lessThanEntry);
328    if (iter2 == type->entries.end() || name.entry != (*iter2)->name) {
329        return {nullptr, nullptr};
330    }
331    return {iter->get(), iter2->get()};
332}
333
334} // namespace aapt
335