Skip to content

Commit b7798cc

Browse files
M2T3a: Adding ResComplexEntryBuilder for easier complex entry creation
Summary: Created a class called `ResComplexEntryBuilder` to easily create complex entries. These methods can be used to easily redefine styles and will eventually be used to edit styles in `.apk` Android apps. Reviewed By: wsanville Differential Revision: D77178719 fbshipit-source-id: 217be712c701a7528a49400832304016c12ec124
1 parent 25cedd0 commit b7798cc

File tree

5 files changed

+147
-26
lines changed

5 files changed

+147
-26
lines changed

libresource/Serialize.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,9 @@ void push_header_with_updated_size(android::ResChunk_header* header,
149149
auto start_pos = out->size();
150150
push_header(header, out);
151151
auto bytes_written = out->size() - start_pos;
152-
LOG_ALWAYS_FATAL_IF(
153-
bytes_written < sizeof(android::ResChunk_header),
154-
"Expected at least %zu header bytes. Actual %zu.",
155-
sizeof(android::ResChunk_header), bytes_written);
152+
LOG_ALWAYS_FATAL_IF(bytes_written < sizeof(android::ResChunk_header),
153+
"Expected at least %zu header bytes. Actual %zu.",
154+
sizeof(android::ResChunk_header), bytes_written);
156155
write_long_at_pos(start_pos + 2 * sizeof(uint16_t), new_size, out);
157156
}
158157

@@ -666,6 +665,24 @@ void ResTableTypeDefiner::serialize(android::Vector<char>* out) {
666665
write_short_at_pos(type_count_pos, type_count, out);
667666
}
668667

668+
void ResComplexEntryBuilder::serialize(android::Vector<char>* out) {
669+
android::ResTable_map_entry entry;
670+
entry.size = sizeof(android::ResTable_map_entry);
671+
entry.flags = android::ResTable_entry::FLAG_COMPLEX;
672+
entry.key.index = m_key_string_index;
673+
entry.parent.ident = m_parent_id;
674+
entry.count = m_attributes.size();
675+
676+
push_struct(entry, out);
677+
678+
for (const auto& attr : m_attributes) {
679+
android::ResTable_map map;
680+
map.name.ident = attr.first;
681+
map.value = attr.second;
682+
push_struct(map, out);
683+
}
684+
}
685+
669686
void ResStringPoolBuilder::add_string(const char* s, size_t len) {
670687
StringHolder holder(s, len);
671688
m_strings.emplace_back(std::move(holder));

libresource/utils/Serialize.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,38 @@ class ResTableTypeDefiner : public ResTableTypeBuilder {
373373
const std::vector<uint32_t> m_flags;
374374
};
375375

376+
/**
377+
* Builder for creating a complex entry (ResTable_map_entry) which is used to
378+
* represent styles in the resource table.
379+
*/
380+
class ResComplexEntryBuilder {
381+
public:
382+
ResComplexEntryBuilder() : m_key_string_index(0), m_parent_id(0) {}
383+
384+
void set_key_string_index(uint32_t index) { m_key_string_index = index; }
385+
void set_parent_id(uint32_t id) { m_parent_id = id; }
386+
void add(uint32_t attr_id, const android::Res_value& value) {
387+
m_attributes.emplace_back(attr_id, value);
388+
}
389+
void add(const android::ResTable_map* map) {
390+
m_attributes.emplace_back(map->name.ident, map->value);
391+
}
392+
void add(uint32_t attr_id, uint8_t data_type, uint32_t data) {
393+
android::Res_value value;
394+
value.size = sizeof(android::Res_value);
395+
value.dataType = data_type;
396+
value.data = data;
397+
m_attributes.emplace_back(attr_id, value);
398+
}
399+
400+
void serialize(android::Vector<char>* out);
401+
402+
private:
403+
uint32_t m_key_string_index;
404+
uint32_t m_parent_id;
405+
std::vector<std::pair<uint32_t, android::Res_value>> m_attributes;
406+
};
407+
376408
// Struct for defining an existing type and the collection of entries in all
377409
// configs.
378410
struct TypeInfo {

test/unit/ResourceSerializationTest.cpp

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -925,15 +925,27 @@ void build_arsc_file_and_validate(
925925
foo_package.id, 2, style_configs, style_flags);
926926
package_builder->add_type(style_type_definer);
927927

928-
style.item0.name.ident = 0x01010098; // android:textColor
929-
style.item0.value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
930-
style.item0.value.data = 0xFF0000FF;
928+
arsc::ResComplexEntryBuilder complex_entry_builder;
929+
complex_entry_builder.set_key_string_index(3);
930+
complex_entry_builder.set_parent_id(0);
931+
932+
std::vector<std::tuple<uint8_t, uint32_t, uint32_t>> attributes = {
933+
{android::Res_value::TYPE_INT_COLOR_RGB8, 0xFF0000FF, 0x01010098},
934+
{android::Res_value::TYPE_INT_COLOR_RGB8, 0xFF00FF00, 0x010100d4}};
935+
936+
for (const auto& [data_type, data_value, attr_id] : attributes) {
937+
android::Res_value value;
938+
value.size = sizeof(android::Res_value);
939+
value.dataType = data_type;
940+
value.data = data_value;
941+
complex_entry_builder.add(attr_id, value);
942+
}
931943

932-
style.item1.name.ident = 0x010100d4; // android:background
933-
style.item1.value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
934-
style.item1.value.data = 0xFF00FF00;
944+
android::Vector<char> complex_entry_data;
945+
complex_entry_builder.serialize(&complex_entry_data);
935946

936-
style_type_definer->add(&xxhdpi_config, &style);
947+
style_type_definer->add(&xxhdpi_config, {(uint8_t*)complex_entry_data.array(),
948+
complex_entry_data.size()});
937949

938950
// Write to a file, give the callback the temp dir and file to validate
939951
// against.
@@ -1005,7 +1017,7 @@ std::vector<arsc::TypeInfo> load_types(const RedexMappedFile& arsc_file) {
10051017
EXPECT_EQ((expected).value.data, __actual_value.data); \
10061018
})
10071019
// Assert values in the table match the two items expecrted in the
1008-
// "MapEntryAndValues"
1020+
// "MapEntryAndTwoValues"
10091021
#define ASSERT_MAP_ENTRY_VALUES(table, config_str, entry_str, expected) \
10101022
({ \
10111023
uint32_t __id = (table).get_identifier(entry_str); \
@@ -1081,9 +1093,21 @@ TEST(ResTable, ComputeSizes) {
10811093
EntryAndValue simple(0, android::Res_value::TYPE_DIMENSION, 1000);
10821094
EXPECT_EQ(arsc::compute_entry_value_length(&simple.entry),
10831095
sizeof(EntryAndValue));
1084-
MapEntryAndValues complex(1, 0);
1085-
EXPECT_EQ(arsc::compute_entry_value_length(&complex.entry),
1086-
sizeof(MapEntryAndValues));
1096+
1097+
arsc::ResComplexEntryBuilder complex_builder;
1098+
complex_builder.set_key_string_index(1);
1099+
complex_builder.add(0x01010098, android::Res_value::TYPE_INT_COLOR_RGB8,
1100+
0xff0000ff);
1101+
1102+
android::Vector<char> complex_entry_data;
1103+
complex_builder.serialize(&complex_entry_data);
1104+
1105+
android::ResTable_entry* complex_entry_ptr =
1106+
(android::ResTable_entry*)complex_entry_data.array();
1107+
1108+
size_t expected_size =
1109+
sizeof(android::ResTable_map_entry) + sizeof(android::ResTable_map);
1110+
EXPECT_EQ(arsc::compute_entry_value_length(complex_entry_ptr), expected_size);
10871111
}
10881112

10891113
TEST(ResTable, DeleteAllEntriesInType) {

test/unit/arsc/TestStructures.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "TestStructures.h"
99

1010
#include "androidfw/ResourceTypes.h"
11+
#include "utils/Serialize.h"
1112

1213
// Sample data for building arsc test cases
1314
EntryAndValue e0(0, android::Res_value::TYPE_DIMENSION, 1000);
@@ -17,7 +18,20 @@ EntryAndValue e2(2, android::Res_value::TYPE_REFERENCE, 0x7f010001);
1718
EntryAndValue id_0(0, android::Res_value::TYPE_INT_BOOLEAN, 0);
1819
EntryAndValue id_1(1, android::Res_value::TYPE_INT_BOOLEAN, 0);
1920
EntryAndValue id_2(2, android::Res_value::TYPE_INT_BOOLEAN, 0);
20-
MapEntryAndValues style(3, 0);
21+
22+
MapEntryAndTwoValues create_style() {
23+
arsc::ResComplexEntryBuilder complex_builder;
24+
complex_builder.set_key_string_index(3);
25+
26+
complex_builder.add(0x01010098, android::Res_value::TYPE_INT_COLOR_RGB8,
27+
0xFF0000FF); // android:textColor
28+
complex_builder.add(0x010100d4, android::Res_value::TYPE_INT_COLOR_RGB8,
29+
0xFF00FF00); // android:background
30+
31+
return MapEntryAndTwoValues(complex_builder);
32+
}
33+
34+
MapEntryAndTwoValues style = create_style();
2135

2236
// The package that many unit tests will be in.
2337
android::ResTable_package foo_package{.id = 0x7f,

test/unit/arsc/TestStructures.h

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "Util.h"
1111
#include "androidfw/ResourceTypes.h"
12+
#include "utils/Serialize.h"
1213

1314
// Data that is used to write many test cases against. Meant to be included from
1415
// individual test cpp files that want to code against it.
@@ -26,18 +27,51 @@ PACKED(struct EntryAndValue {
2627
});
2728

2829
// For testing simplicity, a map that has two items in it.
29-
struct MapEntryAndValues {
30+
struct MapEntryAndTwoValues {
3031
android::ResTable_map_entry entry{};
3132
android::ResTable_map item0{};
3233
android::ResTable_map item1{};
33-
MapEntryAndValues(uint32_t key_string_idx, uint32_t parent_ident) {
34-
entry.size = sizeof(android::ResTable_map_entry);
35-
entry.count = 2;
36-
entry.flags = android::ResTable_entry::FLAG_COMPLEX;
37-
entry.key.index = key_string_idx;
38-
entry.parent.ident = parent_ident;
39-
item0.value.size = sizeof(android::Res_value);
40-
item1.value.size = sizeof(android::Res_value);
34+
35+
template <typename T>
36+
static bool extract_at(const android::Vector<char>& data,
37+
size_t index,
38+
T* result) {
39+
static_assert(std::is_trivially_copyable_v<T>,
40+
"Not trivially copyable, can't safely memcpy");
41+
const size_t size = sizeof(T);
42+
const size_t offset = index * size;
43+
44+
if (offset + size <= data.size()) {
45+
memcpy(result, data.array() + offset, size);
46+
return true;
47+
}
48+
return false;
49+
}
50+
51+
explicit MapEntryAndTwoValues(arsc::ResComplexEntryBuilder builder) {
52+
android::Vector<char> complex_entry_data;
53+
builder.serialize(&complex_entry_data);
54+
55+
const size_t entry_size = sizeof(android::ResTable_map_entry);
56+
const size_t map_size = sizeof(android::ResTable_map);
57+
58+
if (entry_size <= complex_entry_data.size()) {
59+
memcpy(&entry,
60+
complex_entry_data.array(),
61+
sizeof(android::ResTable_map_entry));
62+
63+
auto count = entry.count;
64+
if (count > 0 && entry_size + map_size <= complex_entry_data.size()) {
65+
memcpy(&item0, complex_entry_data.array() + entry_size, map_size);
66+
67+
if (count > 1 &&
68+
entry_size + 2 * map_size <= complex_entry_data.size()) {
69+
memcpy(&item1,
70+
complex_entry_data.array() + entry_size + map_size,
71+
map_size);
72+
}
73+
}
74+
}
4175
}
4276
};
4377

@@ -49,7 +83,7 @@ extern EntryAndValue e2;
4983
extern EntryAndValue id_0;
5084
extern EntryAndValue id_1;
5185
extern EntryAndValue id_2;
52-
extern MapEntryAndValues style;
86+
extern MapEntryAndTwoValues style;
5387

5488
// A package called "foo"
5589
extern android::ResTable_package foo_package;

0 commit comments

Comments
 (0)