blob: 5421abde3689e2c9f0dd697044261143c2bd7691 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "compile/IdAssigner.h"
#include <map>
#include <unordered_map>
#include "android-base/expected.h"
#include "android-base/logging.h"
#include "ResourceTable.h"
#include "process/IResourceTableConsumer.h"
#include "util/Util.h"
using android::base::expected;
using android::base::unexpected;
namespace aapt {
namespace {
template <typename T>
using Result = expected<T, std::string>;
template <typename Id, typename Key>
struct NextIdFinder {
std::map<Id, Key> pre_assigned_ids_;
explicit NextIdFinder(Id start_id = 0u) : next_id_(start_id){};
// Attempts to reserve an identifier for the specified key.
// If the identifier is already reserved by a different key, an error message is returned.
// Reserving identifiers must be completed before `NextId` is called for the first time.
Result<Id> ReserveId(Key key, Id id);
// Retrieves the next available identifier that has not been reserved.
std::optional<Id> NextId();
private:
// Attempts to set `next_id_` to the next available identifier that has not been reserved.
// Returns whether there were any available identifiers.
std::optional<Id> SkipToNextAvailableId();
Id next_id_;
bool next_id_called_ = false;
bool exhausted_ = false;
typename std::map<Id, Key>::iterator next_preassigned_id_;
};
struct TypeGroup {
explicit TypeGroup(uint8_t package_id, uint8_t type_id)
: package_id_(package_id), type_id_(type_id){};
// Attempts to reserve the resource id for the specified resource name.
// If the id is already reserved by a different name, an error message is returned.
// Reserving identifiers must be completed before `NextId` is called for the first time.
Result<std::monostate> ReserveId(const ResourceName& name, ResourceId id);
// Retrieves the next available resource id that has not been reserved.
Result<ResourceId> NextId();
private:
uint8_t package_id_;
uint8_t type_id_;
NextIdFinder<uint16_t, ResourceName> next_entry_id_;
};
struct ResourceTypeKey {
ResourceType type;
uint8_t id;
bool operator<(const ResourceTypeKey& other) const {
return (type != other.type) ? type < other.type : id < other.id;
}
bool operator==(const ResourceTypeKey& other) const {
return type == other.type && id == other.id;
}
bool operator!=(const ResourceTypeKey& other) const {
return !(*this == other);
}
};
::std::ostream& operator<<(::std::ostream& out, const ResourceTypeKey& type) {
return out << type.type;
}
struct IdAssignerContext {
IdAssignerContext(std::string package_name, uint8_t package_id)
: package_name_(std::move(package_name)), package_id_(package_id) {
}
// Attempts to reserve the resource id for the specified resource name.
// Returns whether the id was reserved successfully.
// Reserving identifiers must be completed before `NextId` is called for the first time.
bool ReserveId(const ResourceName& name, ResourceId id, const Visibility& visibility,
android::IDiagnostics* diag);
// Retrieves the next available resource id that has not been reserved.
std::optional<ResourceId> NextId(const ResourceName& name, android::IDiagnostics* diag);
private:
std::string package_name_;
uint8_t package_id_;
std::map<ResourceTypeKey, TypeGroup> types_;
std::map<ResourceType, uint8_t> non_staged_type_ids_;
NextIdFinder<uint8_t, ResourceTypeKey> type_id_finder_ =
NextIdFinder<uint8_t, ResourceTypeKey>(1);
};
} // namespace
bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
IdAssignerContext assigned_ids(context->GetCompilationPackage(), context->GetPackageId());
for (auto& package : table->packages) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
const ResourceName name(package->name, type->named_type, entry->name);
if (entry->id && !assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
context->GetDiagnostics())) {
return false;
}
auto v = entry->visibility;
v.staged_api = true;
if (entry->staged_id && !assigned_ids.ReserveId(name, entry->staged_id.value().id, v,
context->GetDiagnostics())) {
return false;
}
if (assigned_id_map_) {
// Assign the pre-assigned stable ID meant for this resource.
const auto iter = assigned_id_map_->find(name);
if (iter != assigned_id_map_->end()) {
const ResourceId assigned_id = iter->second;
if (!assigned_ids.ReserveId(name, assigned_id, entry->visibility,
context->GetDiagnostics())) {
return false;
}
entry->id = assigned_id;
}
}
}
}
}
if (assigned_id_map_) {
// Reserve all the IDs mentioned in the stable ID map. That way we won't assign IDs that were
// listed in the map if they don't exist in the table.
for (const auto& stable_id_entry : *assigned_id_map_) {
const ResourceName& pre_assigned_name = stable_id_entry.first;
const ResourceId& pre_assigned_id = stable_id_entry.second;
if (!assigned_ids.ReserveId(pre_assigned_name, pre_assigned_id, {},
context->GetDiagnostics())) {
return false;
}
}
}
// Assign any resources without IDs the next available ID. Gaps will be filled if possible,
// unless those IDs have been reserved.
for (auto& package : table->packages) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
const ResourceName name(package->name, type->named_type, entry->name);
if (entry->id) {
continue;
}
auto id = assigned_ids.NextId(name, context->GetDiagnostics());
if (!id.has_value()) {
return false;
}
entry->id = id.value();
}
}
}
return true;
}
namespace {
static const std::string_view staged_type_overlap_error =
"Staged public resource type IDs have conflict with non staged public resources type "
"IDs, please restart staged resource type ID assignment at 0xff in public-staging.xml "
"and also delete all the overlapping groups in public-final.xml";
template <typename Id, typename Key>
Result<Id> NextIdFinder<Id, Key>::ReserveId(Key key, Id id) {
CHECK(!next_id_called_) << "ReserveId cannot be called after NextId";
auto assign_result = pre_assigned_ids_.emplace(id, key);
if (!assign_result.second && assign_result.first->second != key) {
std::stringstream error;
error << "ID is already assigned to " << assign_result.first->second;
return unexpected(error.str());
}
return id;
}
template <typename Id, typename Key>
std::optional<Id> NextIdFinder<Id, Key>::NextId() {
if (!next_id_called_) {
next_id_called_ = true;
next_preassigned_id_ = pre_assigned_ids_.begin();
}
return SkipToNextAvailableId();
}
template <typename Id, typename Key>
std::optional<Id> NextIdFinder<Id, Key>::SkipToNextAvailableId() {
if (exhausted_) {
return {};
}
while (next_preassigned_id_ != pre_assigned_ids_.end()) {
if (next_preassigned_id_->first == next_id_) {
if (next_id_ == std::numeric_limits<Id>::max()) {
// The last identifier was reserved so there are no more available identifiers.
exhausted_ = true;
return {};
}
++next_id_;
++next_preassigned_id_;
continue;
}
CHECK(next_preassigned_id_->first > next_id_) << "Preassigned IDs are not in sorted order";
break;
}
if (next_id_ == std::numeric_limits<Id>::max()) {
// There are no more identifiers after this one, but this one is still available so return it.
exhausted_ = true;
}
return next_id_++;
}
Result<std::monostate> TypeGroup::ReserveId(const ResourceName& name, ResourceId id) {
if (type_id_ != id.type_id()) {
// Currently there cannot be multiple type ids for a single type.
std::stringstream error;
error << "type '" << name.type << "' already has ID " << std::hex << (int)type_id_;
return unexpected(error.str());
}
auto assign_result = next_entry_id_.ReserveId(name, id.entry_id());
if (!assign_result.has_value()) {
std::stringstream error;
error << "entry " << assign_result.error();
return unexpected(error.str());
}
return {};
}
Result<ResourceId> TypeGroup::NextId() {
auto entry_id = next_entry_id_.NextId();
if (!entry_id.has_value()) {
std::stringstream error;
error << "resource type ID has exceeded the maximum number of resource entries ("
<< (std::numeric_limits<uint16_t>::max() + 1u) << ")";
return unexpected(error.str());
}
return ResourceId(package_id_, type_id_, entry_id.value());
}
bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id,
const Visibility& visibility, android::IDiagnostics* diag) {
if (package_id_ != id.package_id()) {
diag->Error(android::DiagMessage()
<< "can't assign ID " << id << " to resource " << name
<< " because package already has ID " << std::hex << (int)id.package_id());
return false;
}
auto key = ResourceTypeKey{name.type.type, id.type_id()};
auto type = types_.find(key);
if (type == types_.end()) {
// The type has not been assigned an id yet. Ensure that the specified id is not being used by
// another type.
auto assign_result = type_id_finder_.ReserveId(key, id.type_id());
if (!assign_result.has_value()) {
auto pre_assigned_type = type_id_finder_.pre_assigned_ids_[id.type_id()].type;
bool pre_assigned_type_staged =
non_staged_type_ids_.find(pre_assigned_type) == non_staged_type_ids_.end();
auto hex_type_id = fmt::format("{:#04x}", (int)id.type_id());
bool current_type_staged = visibility.staged_api;
diag->Error(android::DiagMessage()
<< "can't assign type ID " << hex_type_id << " to "
<< (current_type_staged ? "staged type " : "non staged type ") << name.type.type
<< " because this type ID have been assigned to "
<< (pre_assigned_type_staged ? "staged type " : "non staged type ")
<< pre_assigned_type);
if (pre_assigned_type_staged || current_type_staged) {
diag->Error(android::DiagMessage() << staged_type_overlap_error);
}
return false;
}
type = types_.emplace(key, TypeGroup(package_id_, id.type_id())).first;
}
if (!visibility.staged_api) {
// Ensure that non-staged resources can only exist in one type ID.
auto non_staged_type = non_staged_type_ids_.emplace(name.type.type, id.type_id());
if (!non_staged_type.second && non_staged_type.first->second != id.type_id()) {
diag->Error(android::DiagMessage()
<< "can't assign ID " << id << " to resource " << name
<< " because type already has ID " << std::hex << (int)id.type_id());
return false;
}
} else {
// Ensure that staged public resources cannot have the same type name and type id with
// non staged public resources.
auto non_staged_type = non_staged_type_ids_.find(name.type.type);
if (non_staged_type != non_staged_type_ids_.end() && non_staged_type->second == id.type_id()) {
diag->Error(
android::DiagMessage()
<< "can`t assign type ID " << fmt::format("{:#04x}", (int)id.type_id())
<< " to staged type " << name.type.type << " because type ID "
<< fmt::format("{:#04x}", (int)id.type_id())
<< " already has been assigned to a non staged resource type with the same type name");
diag->Error(android::DiagMessage() << staged_type_overlap_error);
return false;
}
}
auto assign_result = type->second.ReserveId(name, id);
if (!assign_result.has_value()) {
diag->Error(android::DiagMessage() << "can't assign ID " << id << " to resource " << name
<< " because " << assign_result.error());
return false;
}
return true;
}
std::optional<ResourceId> IdAssignerContext::NextId(const ResourceName& name,
android::IDiagnostics* diag) {
// The package name is not known during the compile stage.
// Resources without a package name are considered a part of the app being linked.
CHECK(name.package.empty() || name.package == package_name_);
// Find the type id for non-staged resources of this type.
auto non_staged_type = non_staged_type_ids_.find(name.type.type);
if (non_staged_type == non_staged_type_ids_.end()) {
auto next_type_id = type_id_finder_.NextId();
CHECK(next_type_id.has_value()) << "resource type IDs allocated have exceeded maximum (256)";
non_staged_type = non_staged_type_ids_.emplace(name.type.type, *next_type_id).first;
}
ResourceTypeKey key{name.type.type, non_staged_type->second};
auto type = types_.find(key);
if (type == types_.end()) {
type = types_.emplace(key, TypeGroup(package_id_, key.id)).first;
}
auto assign_result = type->second.NextId();
if (!assign_result.has_value()) {
diag->Error(android::DiagMessage() << "can't assign resource ID to resource " << name
<< " because " << assign_result.error());
return {};
}
return assign_result.value();
}
} // namespace
} // namespace aapt