Skip to content

Commit

Permalink
Implement serializer support for new ResTable_typeSpec header field
Browse files Browse the repository at this point in the history
Summary:
This field used to be reserved (0) but has been repurposed in recent versions of Android tooling: https://cs.android.com/android/_/android/platform/frameworks/base/+/4f48ffd43b182f31a199e54a39406cbd3d84abb0

This change has been released in binary form in aapt2 for a while, but has not been merged into any of the public AOSP branches until quite recently (so it was unclear what this field was used for previously).

Make the serializer in Redex spit out the value as indicated, count the number of ResTable_type that are emitted then stamp in the header value.

Reviewed By: NTillmann

Differential Revision: D63565323

fbshipit-source-id: d0e7e27377682c1ce8d52195a1ba00c612a9f382
  • Loading branch information
wsanville authored and facebook-github-bot committed Oct 22, 2024
1 parent 627e922 commit 49c93c7
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 18 deletions.
35 changes: 27 additions & 8 deletions libresource/Serialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ namespace {
// Just a random thing to make it easy to see (when dumping bytes) if we forgot
// to go back and correct a chunk size.
constexpr uint32_t FILL_IN_LATER = 0xEEEEEEEE;
constexpr uint16_t FILL_IN_LATER_SHORT = 0xEEEE;

void write_long_at_pos(size_t index,
uint32_t data,
Expand All @@ -81,6 +82,14 @@ void write_long_at_pos(size_t index,
vec->replaceAt((char)(swapped >> 24), index + 3);
}

void write_short_at_pos(size_t index,
uint16_t data,
android::Vector<char>* vec) {
auto swapped = htods(data);
vec->replaceAt((char)swapped, index);
vec->replaceAt((char)(swapped >> 8), index + 1);
}

void encode_string8(const char* string,
size_t& len,
android::Vector<char>* vec) {
Expand Down Expand Up @@ -373,13 +382,13 @@ void ResTableTypeBuilder::encode_offsets_as_sparse(
}
}

void ResTableTypeProjector::serialize_type(android::ResTable_type* type,
bool ResTableTypeProjector::serialize_type(android::ResTable_type* type,
size_t last_non_deleted,
android::Vector<char>* out) {
if (dtohl(type->entryCount) == 0 || dtohs(type->entriesStart) == 0) {
// Wonky input data, omit this config.
ALOGD("Wonky config for type %d, dropping!", type->id);
return;
return false;
}
// Check if this config has all of its entries deleted. If a non-default
// config has everything deleted, skip emitting data.
Expand All @@ -397,7 +406,7 @@ void ResTableTypeProjector::serialize_type(android::ResTable_type* type,
}
if (num_non_deleted_non_empty_entries == 0) {
// No meaningful values for this config, don't emit the struct.
return;
return false;
}
}
// Write entry/value data by iterating the existing offset data again, and
Expand Down Expand Up @@ -474,6 +483,7 @@ void ResTableTypeProjector::serialize_type(android::ResTable_type* type,
push_long(offsets[i], out);
}
push_vec(temp, out);
return true;
}

void ResTableTypeProjector::serialize(android::Vector<char>* out) {
Expand Down Expand Up @@ -507,8 +517,9 @@ void ResTableTypeProjector::serialize(android::Vector<char>* out) {
push_long(total_size, out);
out->push_back(m_type);
out->push_back(0);
out->push_back(0);
out->push_back(0);
// Number of types (used to be a reserved field). Will be stamped in later.
auto type_count_pos = out->size();
push_short(FILL_IN_LATER_SHORT, out);
push_long(entries, out);
// Copy all existing spec flags for non-deleted entries
for (uint16_t i = 0; i < original_entries; i++) {
Expand All @@ -521,9 +532,13 @@ void ResTableTypeProjector::serialize(android::Vector<char>* out) {
}
// Write all applicable ResTable_type structures (and their corresponding
// entries/values).
uint16_t type_count{0};
for (size_t i = 0; i < m_configs.size(); i++) {
serialize_type(m_configs.at(i), last_non_deleted, out);
if (serialize_type(m_configs.at(i), last_non_deleted, out)) {
type_count++;
}
}
write_short_at_pos(type_count_pos, type_count, out);
}

void ResTableTypeDefiner::serialize(android::Vector<char>* out) {
Expand Down Expand Up @@ -561,18 +576,21 @@ void ResTableTypeDefiner::serialize(android::Vector<char>* out) {
push_long(total_size, out);
out->push_back(m_type);
out->push_back(0);
out->push_back(0);
out->push_back(0);
// Number of types (used to be a reserved field). Will be stamped in later.
auto type_count_pos = out->size();
push_short(FILL_IN_LATER_SHORT, out);
push_long(entries, out);
// Write all given spec flags
for (uint16_t i = 0; i < entries; i++) {
push_long(dtohl(m_flags.at(i)), out);
}
// Write the N configs given and all their entries/values
uint16_t type_count{0};
for (auto& config : m_configs) {
if (empty_configs.count(config) > 0) {
continue;
}
type_count++;
auto& data = m_data.at(config);
// Compute offsets and entry/value data size.
CanonicalEntries canonical_entries;
Expand Down Expand Up @@ -626,6 +644,7 @@ void ResTableTypeDefiner::serialize(android::Vector<char>* out) {
}
push_vec(entry_data, out);
}
write_short_at_pos(type_count_pos, type_count, out);
}

void ResStringPoolBuilder::add_string(const char* s, size_t len) {
Expand Down
2 changes: 1 addition & 1 deletion libresource/utils/Serialize.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ class ResTableTypeProjector : public ResTableTypeBuilder {
virtual ~ResTableTypeProjector() {}

private:
void serialize_type(android::ResTable_type*,
bool serialize_type(android::ResTable_type*,
size_t,
android::Vector<char>* out);
android::ResTable_typeSpec* m_spec;
Expand Down
62 changes: 53 additions & 9 deletions test/unit/ResourceSerializationTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -846,16 +846,30 @@ TEST(Configs, TestConfigEquivalence) {
}
}

TEST(ResTable, TestBuilderRoundTrip) {
auto tmp_dir = redex::make_tmp_dir("ResTable%%%%%%%%");
auto res_path = tmp_dir.path + "/resources.arsc";
copy_file(get_env("test_arsc_path"), res_path);
ResourcesArscFile res_table(res_path);
res_table.serialize();
EXPECT_TRUE(are_files_equal(get_env("test_arsc_path"), res_path));
}

namespace {
class TypeSpecCollector : public arsc::ResourceTableVisitor {
public:
bool visit_type_spec(android::ResTable_package* package,
android::ResTable_typeSpec* type_spec) override {
type_specs.emplace(type_spec->id, type_spec);
return arsc::ResourceTableVisitor::visit_type_spec(package, type_spec);
}

// Sets the reserved short to 0, like older tool versions would do.
void clear_reserved_fields() {
for (auto&& [id, ptr] : type_specs) {
ptr->res1 = 0;
}
}

// Get the repurposed short field in the header.
uint16_t get_reserved_field(uint8_t type_id) {
return type_specs.at(type_id)->res1;
}

std::map<uint8_t, android::ResTable_typeSpec*> type_specs;
};

void build_arsc_file_and_validate(
const std::function<void(const std::string& temp_dir,
const std::string& arsc_path)>& callback) {
Expand Down Expand Up @@ -1022,6 +1036,23 @@ std::vector<arsc::TypeInfo> load_types(const RedexMappedFile& arsc_file) {
})
} // namespace

TEST(ResTable, TestBuilderRoundTrip) {
auto tmp_dir = redex::make_tmp_dir("ResTable%%%%%%%%");
auto res_path = tmp_dir.path + "/resources.arsc";
copy_file(get_env("test_arsc_path"), res_path);
ResourcesArscFile res_table(res_path);
res_table.serialize();
// To match the old, checked in binary that was made with outdated aapt tool,
// stamp over the reserved fields that got repurposed to set them to be zero.
{
auto f = RedexMappedFile::open(res_path, false);
TypeSpecCollector collector;
collector.visit(f.data(), f.size());
collector.clear_reserved_fields();
}
EXPECT_TRUE(are_files_equal(get_env("test_arsc_path"), res_path));
}

TEST(ResTable, BuildNewTable) {
build_arsc_file_and_validate([&](const std::string& /* unused */,
const std::string& arsc_path) {
Expand All @@ -1038,6 +1069,14 @@ TEST(ResTable, BuildNewTable) {
ASSERT_ENTRY_VALUES(table_dump, "land", "foo:dimen/first", e0_land);
// Separate validation for plurals, styles, etc.
ASSERT_MAP_ENTRY_VALUES(table_dump, "xxhdpi", "foo:style/fourth", style);
// Sanity check header values for number of types in each type spec.
auto f = RedexMappedFile::open(arsc_path, false);
TypeSpecCollector collector;
collector.visit(f.data(), f.size());
ASSERT_EQ(collector.get_reserved_field(0x1), 2)
<< "dimen should have two ResTable_type entries";
ASSERT_EQ(collector.get_reserved_field(0x2), 1)
<< "style should have one ResTable_type entry";
});
}

Expand Down Expand Up @@ -1621,6 +1660,11 @@ TEST(ResTable, BuildDumpAndParseSparseType) {
EXPECT_EQ(types.at(1)->header.size,
sizeof(android::ResTable_type) + size_of_entry_value_and_offset)
<< "Expected small type size due to sparse encoding";
// Additional validation that ResTableTypeProjector filled out headers
// properly.
EXPECT_EQ(parsed_table.m_types.begin()->second->res1, 2)
<< "First ResTable_typeSpec should specify two ResTable_type structs "
"to follow";
}
}

Expand Down

0 comments on commit 49c93c7

Please sign in to comment.