androidfw: Add support for compact resource entries
Bug: 237583012
Given the large number of simple resources such as strings in
Android resources, their ResTable_entry and Res_value can be
encoded together in a compact way. This allows a significant
saving in both storage and memory footprint.
The basic observations for simple resources are:
* ResTable_entry.size will always be sizeof(ResTable_entry)
unless it's a complex entry
* ResTable_entry.key is unlikely to exceed 16-bit
* ResTable_entry.flags only uses 3 bits for now
* Res_value.size will always be sizeof(Res_value)
Given the above, we could well encode the information into
a compact/compatible structure.
struct compact {
uint16_t key;
uint16_t flags;
uint32_t data;
};
The layout of this structure will allow maximum backward
compatibility. e.g. the flags will be at the same offset,
and a
`dtohs((ResTable_entry *)entry->flags) & FLAG_COMPACT`
would tell if this entry is a compact one or not. For a
compact entry:
struct compact *entry;
entry_size == sizeof(*entry)
entry_key == static_cast<uint32_t>(dtohs(entry->key))
entry_flags == dtohs(entry->flags) & 0xff // low 8-bit
data_type == dtohs(entry->flags) >> 8 // high 8-bit
data_size == sizeof(Res_value)
data_value == dtohl(entry->data)
To allow minimum code change and backward compatibility,
we change 'struct ResTable_entry' to 'union ResTable_entry',
with an anonymous structure inside that's fully backward
compatible. Thus, any existing reference such as:
ResTable_entry *entry = ...
if (dtohs(entry->flags) & ResTable_entry::FLAG_COMPLEX) ...
would still work.
However, special care needs to be taken after an entry is
obtained, and when value needs to be extracted.
A compact entry will not encode a complex value, and hence
complex entries/values are handled the same way.
Change-Id: I15d97a4f5e85fab28c075496f7f0cf6b1fcd73e3
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 1381bdd..06ffb72 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -43,28 +43,19 @@
using EntryValue = std::variant<Res_value, incfs::verified_map_ptr<ResTable_map_entry>>;
+/* NOTE: table_entry has been verified in LoadedPackage::GetEntryFromOffset(),
+ * and so access to ->value() and ->map_entry() are safe here
+ */
base::expected<EntryValue, IOError> GetEntryValue(
incfs::verified_map_ptr<ResTable_entry> table_entry) {
- const uint16_t entry_size = dtohs(table_entry->size);
+ const uint16_t entry_size = table_entry->size();
// Check if the entry represents a bag value.
- if (entry_size >= sizeof(ResTable_map_entry) &&
- (dtohs(table_entry->flags) & ResTable_entry::FLAG_COMPLEX)) {
- const auto map_entry = table_entry.convert<ResTable_map_entry>();
- if (!map_entry) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
- return map_entry.verified();
+ if (entry_size >= sizeof(ResTable_map_entry) && table_entry->is_complex()) {
+ return table_entry.convert<ResTable_map_entry>().verified();
}
- // The entry represents a non-bag value.
- const auto entry_value = table_entry.offset(entry_size).convert<Res_value>();
- if (!entry_value) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
- Res_value value;
- value.copyFrom_dtoh(entry_value.value());
- return value;
+ return table_entry->value();
}
} // namespace
@@ -814,17 +805,12 @@
return base::unexpected(std::nullopt);
}
- auto best_entry_result = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
- if (!best_entry_result.has_value()) {
- return base::unexpected(best_entry_result.error());
+ auto best_entry_verified = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
+ if (!best_entry_verified.has_value()) {
+ return base::unexpected(best_entry_verified.error());
}
- const incfs::map_ptr<ResTable_entry> best_entry = *best_entry_result;
- if (!best_entry) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
-
- const auto entry = GetEntryValue(best_entry.verified());
+ const auto entry = GetEntryValue(*best_entry_verified);
if (!entry.has_value()) {
return base::unexpected(entry.error());
}
@@ -837,7 +823,7 @@
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
.entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(),
- best_entry->key.index),
+ (*best_entry_verified)->key()),
.dynamic_ref_table = package_group.dynamic_ref_table.get(),
};
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 5b69cca..e78f91e 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -107,8 +107,8 @@
return true;
}
-static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
- incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
+static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+VerifyResTableEntry(incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
// Check that the offset is aligned.
if (UNLIKELY(entry_offset & 0x03U)) {
LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
@@ -136,7 +136,7 @@
return base::unexpected(IOError::PAGES_MISSING);
}
- const size_t entry_size = dtohs(entry->size);
+ const size_t entry_size = entry->size();
if (UNLIKELY(entry_size < sizeof(entry.value()))) {
LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
<< " is too small.";
@@ -149,6 +149,11 @@
return base::unexpected(std::nullopt);
}
+ // If entry is compact, value is already encoded, and a compact entry
+ // cannot be a map_entry, we are done verifying
+ if (entry->is_compact())
+ return entry.verified();
+
if (entry_size < sizeof(ResTable_map_entry)) {
// There needs to be room for one Res_value struct.
if (UNLIKELY(entry_offset + entry_size > chunk_size - sizeof(Res_value))) {
@@ -192,7 +197,7 @@
return base::unexpected(std::nullopt);
}
}
- return {};
+ return entry.verified();
}
LoadedPackage::iterator::iterator(const LoadedPackage* lp, size_t ti, size_t ei)
@@ -228,7 +233,7 @@
entryIndex_);
}
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index) {
base::expected<uint32_t, NullOrIOError> entry_offset = GetEntryOffset(type_chunk, entry_index);
if (UNLIKELY(!entry_offset.has_value())) {
@@ -297,13 +302,14 @@
return value;
}
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntryFromOffset(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset) {
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+LoadedPackage::GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk,
+ uint32_t offset) {
auto valid = VerifyResTableEntry(type_chunk, offset);
if (UNLIKELY(!valid.has_value())) {
return base::unexpected(valid.error());
}
- return type_chunk.offset(offset + dtohl(type_chunk->entriesStart)).convert<ResTable_entry>();
+ return valid;
}
base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations(
@@ -400,7 +406,7 @@
return base::unexpected(IOError::PAGES_MISSING);
}
- if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) {
+ if (entry->key() == static_cast<uint32_t>(*key_idx)) {
// The package ID will be overridden by the caller (due to runtime assignment of package
// IDs for shared libraries).
return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 46fbe7c..aac52b4 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4415,20 +4415,14 @@
return err;
}
- if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (entry.entry->map_entry()) {
if (!mayBeBag) {
ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
}
return BAD_VALUE;
}
- const Res_value* value = reinterpret_cast<const Res_value*>(
- reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);
-
- outValue->size = dtohs(value->size);
- outValue->res0 = value->res0;
- outValue->dataType = value->dataType;
- outValue->data = dtohl(value->data);
+ *outValue = entry.entry->value();
// The reference may be pointing to a resource in a shared library. These
// references have build-time generated package IDs. These ids may not match
@@ -4619,11 +4613,10 @@
return err;
}
- const uint16_t entrySize = dtohs(entry.entry->size);
- const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0;
- const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0;
+ const uint16_t entrySize = entry.entry->size();
+ const ResTable_map_entry* map_entry = entry.entry->map_entry();
+ const uint32_t parent = map_entry ? dtohl(map_entry->parent.ident) : 0;
+ const uint32_t count = map_entry ? dtohl(map_entry->count) : 0;
size_t N = count;
@@ -4687,7 +4680,7 @@
// Now merge in the new attributes...
size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type))
- + dtohs(entry.entry->size);
+ + entrySize;
const ResTable_map* map;
bag_entry* entries = (bag_entry*)(set+1);
size_t curEntry = 0;
@@ -5065,7 +5058,7 @@
continue;
}
- if (dtohl(entry->key.index) == (size_t) *ei) {
+ if (entry->key() == (size_t) *ei) {
uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
if (outTypeSpecFlags) {
Entry result;
@@ -6579,8 +6572,8 @@
const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
- if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
+ if (entry->size() < sizeof(*entry)) {
+ ALOGW("ResTable_entry size 0x%zx is too small", entry->size());
return BAD_TYPE;
}
@@ -6591,7 +6584,7 @@
outEntry->specFlags = specFlags;
outEntry->package = bestPackage;
outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
- outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
+ outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, entry->key());
}
return NO_ERROR;
}
@@ -7662,7 +7655,7 @@
continue;
}
- uintptr_t esize = dtohs(ent->size);
+ uintptr_t esize = ent->size();
if ((esize&0x3) != 0) {
printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
continue;
@@ -7674,30 +7667,27 @@
}
const Res_value* valuePtr = NULL;
- const ResTable_map_entry* bagPtr = NULL;
+ const ResTable_map_entry* bagPtr = ent->map_entry();
Res_value value;
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (bagPtr) {
printf("<bag>");
- bagPtr = (const ResTable_map_entry*)ent;
} else {
- valuePtr = (const Res_value*)
- (((const uint8_t*)ent) + esize);
- value.copyFrom_dtoh(*valuePtr);
+ value = ent->value();
printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
(int)value.dataType, (int)value.data,
(int)value.size, (int)value.res0);
}
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
+ if (ent->flags() & ResTable_entry::FLAG_PUBLIC) {
printf(" (PUBLIC)");
}
printf("\n");
if (inclValues) {
- if (valuePtr != NULL) {
+ if (bagPtr == NULL) {
printf(" ");
print_value(typeConfigs->package, value);
- } else if (bagPtr != NULL) {
+ } else {
const int N = dtohl(bagPtr->count);
const uint8_t* baseMapPtr = (const uint8_t*)ent;
size_t mapOffset = esize;
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
index 647aa19..2c39a81 100644
--- a/libs/androidfw/TypeWrappers.cpp
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -91,11 +91,11 @@
if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) {
ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex);
return NULL;
- } else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) {
+ } else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) {
ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex);
return NULL;
- } else if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size));
+ } else if (entry->size() < sizeof(*entry)) {
+ ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size());
return NULL;
}
return entry;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index e459639..79d96282 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -166,14 +166,14 @@
base::expected<uint32_t, NullOrIOError> FindEntryByName(const std::u16string& type_name,
const std::u16string& entry_name) const;
- static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntry(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
+ static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+ GetEntry(incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
static base::expected<uint32_t, NullOrIOError> GetEntryOffset(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
- static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntryFromOffset(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
+ static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+ GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
// Returns the string pool where type names are stored.
const ResStringPool* GetTypeStringPool() const {
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 24628cd..42d8cbe 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -26,6 +26,7 @@
#include <androidfw/Errors.h>
#include <androidfw/LocaleData.h>
#include <androidfw/StringPiece.h>
+#include <utils/ByteOrder.h>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <utils/Vector.h>
@@ -1490,6 +1491,8 @@
static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t),
"ResTable_sparseTypeEntry must be 4 bytes in size");
+struct ResTable_map_entry;
+
/**
* This is the beginning of information about an entry in the resource
* table. It holds the reference to the name of this entry, and is
@@ -1497,12 +1500,11 @@
* * A Res_value structure, if FLAG_COMPLEX is -not- set.
* * An array of ResTable_map structures, if FLAG_COMPLEX is set.
* These supply a set of name/value mappings of data.
+ * * If FLAG_COMPACT is set, this entry is a compact entry for
+ * simple values only
*/
-struct ResTable_entry
+union ResTable_entry
{
- // Number of bytes in this structure.
- uint16_t size;
-
enum {
// If set, this is a complex entry, holding a set of name/value
// mappings. It is followed by an array of ResTable_map structures.
@@ -1514,18 +1516,91 @@
// resources of the same name/type. This is only useful during
// linking with other resource tables.
FLAG_WEAK = 0x0004,
+ // If set, this is a compact entry with data type and value directly
+ // encoded in the this entry, see ResTable_entry::compact
+ FLAG_COMPACT = 0x0008,
};
- uint16_t flags;
-
- // Reference into ResTable_package::keyStrings identifying this entry.
- struct ResStringPool_ref key;
+
+ struct Full {
+ // Number of bytes in this structure.
+ uint16_t size;
+
+ uint16_t flags;
+
+ // Reference into ResTable_package::keyStrings identifying this entry.
+ struct ResStringPool_ref key;
+ } full;
+
+ /* A compact entry is indicated by FLAG_COMPACT, with flags at the same
+ * offset as a normal entry. This is only for simple data values where
+ *
+ * - size for entry or value can be inferred (both being 8 bytes).
+ * - key index is encoded in 16-bit
+ * - dataType is encoded as the higher 8-bit of flags
+ * - data is encoded directly in this entry
+ */
+ struct Compact {
+ uint16_t key;
+ uint16_t flags;
+ uint32_t data;
+ } compact;
+
+ uint16_t flags() const { return dtohs(full.flags); };
+ bool is_compact() const { return flags() & FLAG_COMPACT; }
+ bool is_complex() const { return flags() & FLAG_COMPLEX; }
+
+ size_t size() const {
+ return is_compact() ? sizeof(ResTable_entry) : dtohs(this->full.size);
+ }
+
+ uint32_t key() const {
+ return is_compact() ? dtohs(this->compact.key) : dtohl(this->full.key.index);
+ }
+
+ /* Always verify the memory associated with this entry and its value
+ * before calling value() or map_entry()
+ */
+ Res_value value() const {
+ Res_value v;
+ if (is_compact()) {
+ v.size = sizeof(Res_value);
+ v.res0 = 0;
+ v.data = dtohl(this->compact.data);
+ v.dataType = dtohs(compact.flags) >> 8;
+ } else {
+ auto vaddr = reinterpret_cast<const uint8_t*>(this) + dtohs(this->full.size);
+ auto value = reinterpret_cast<const Res_value*>(vaddr);
+ v.size = dtohs(value->size);
+ v.res0 = value->res0;
+ v.data = dtohl(value->data);
+ v.dataType = value->dataType;
+ }
+ return v;
+ }
+
+ const ResTable_map_entry* map_entry() const {
+ return is_complex() && !is_compact() ?
+ reinterpret_cast<const ResTable_map_entry*>(this) : nullptr;
+ }
};
+/* Make sure size of ResTable_entry::Full and ResTable_entry::Compact
+ * be the same as ResTable_entry. This is to allow iteration of entries
+ * to work in either cases.
+ *
+ * The offset of flags must be at the same place for both structures,
+ * to ensure the correct reading to decide whether this is a full entry
+ * or a compact entry.
+ */
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Full));
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Compact));
+static_assert(offsetof(ResTable_entry, full.flags) == offsetof(ResTable_entry, compact.flags));
+
/**
* Extended form of a ResTable_entry for map entries, defining a parent map
* resource from which to inherit values.
*/
-struct ResTable_map_entry : public ResTable_entry
+struct ResTable_map_entry : public ResTable_entry::Full
{
// Resource identifier of the parent mapping, or 0 if there is none.
// This is always treated as a TYPE_DYNAMIC_REFERENCE.
diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
index d69abe5..aba3ab3 100644
--- a/libs/androidfw/tests/TypeWrappers_test.cpp
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -37,8 +37,8 @@
offsets[0] = 0;
ResTable_entry e1;
memset(&e1, 0, sizeof(e1));
- e1.size = sizeof(e1);
- e1.key.index = 0;
+ e1.full.size = sizeof(e1);
+ e1.full.key.index = 0;
t.header.size += sizeof(e1);
Res_value v1;
@@ -50,8 +50,8 @@
offsets[2] = sizeof(e1) + sizeof(v1);
ResTable_entry e2;
memset(&e2, 0, sizeof(e2));
- e2.size = sizeof(e2);
- e2.key.index = 1;
+ e2.full.size = sizeof(e2);
+ e2.full.key.index = 1;
t.header.size += sizeof(e2);
Res_value v2;
@@ -83,7 +83,7 @@
TypeVariant::iterator iter = v.beginEntries();
ASSERT_EQ(uint32_t(0), iter.index());
ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(0), iter->key.index);
+ ASSERT_EQ(uint32_t(0), iter->full.key.index);
ASSERT_NE(v.endEntries(), iter);
iter++;
@@ -96,7 +96,7 @@
ASSERT_EQ(uint32_t(2), iter.index());
ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(1), iter->key.index);
+ ASSERT_EQ(uint32_t(1), iter->full.key.index);
ASSERT_NE(v.endEntries(), iter);
iter++;