AAPT2: Fix issue with enums and integer attributes
When an attribute had the format "enum|integer", and a max or min
allowed value set, any value set for this attribute would have its
enum symbol's value checked against the valid integer range.
This would lead to the following:
android:numColumns="autofit"
being interpreted as an integer value of -1, which violated the minimum
expected value for numColumns, which was 0.
Bug: 62358540
Test: make aapt2_tests
Change-Id: I3150410448a533d3595a08ac6b2966264db874d8
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index e808984..947e091 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -533,75 +533,119 @@
}
}
-static void BuildAttributeMismatchMessage(DiagMessage* msg,
- const Attribute* attr,
- const Item* value) {
- *msg << "expected";
- if (attr->type_mask & android::ResTable_map::TYPE_BOOLEAN) {
- *msg << " boolean";
+static void BuildAttributeMismatchMessage(const Attribute& attr, const Item& value,
+ DiagMessage* out_msg) {
+ *out_msg << "expected";
+ if (attr.type_mask & android::ResTable_map::TYPE_BOOLEAN) {
+ *out_msg << " boolean";
}
- if (attr->type_mask & android::ResTable_map::TYPE_COLOR) {
- *msg << " color";
+ if (attr.type_mask & android::ResTable_map::TYPE_COLOR) {
+ *out_msg << " color";
}
- if (attr->type_mask & android::ResTable_map::TYPE_DIMENSION) {
- *msg << " dimension";
+ if (attr.type_mask & android::ResTable_map::TYPE_DIMENSION) {
+ *out_msg << " dimension";
}
- if (attr->type_mask & android::ResTable_map::TYPE_ENUM) {
- *msg << " enum";
+ if (attr.type_mask & android::ResTable_map::TYPE_ENUM) {
+ *out_msg << " enum";
}
- if (attr->type_mask & android::ResTable_map::TYPE_FLAGS) {
- *msg << " flags";
+ if (attr.type_mask & android::ResTable_map::TYPE_FLAGS) {
+ *out_msg << " flags";
}
- if (attr->type_mask & android::ResTable_map::TYPE_FLOAT) {
- *msg << " float";
+ if (attr.type_mask & android::ResTable_map::TYPE_FLOAT) {
+ *out_msg << " float";
}
- if (attr->type_mask & android::ResTable_map::TYPE_FRACTION) {
- *msg << " fraction";
+ if (attr.type_mask & android::ResTable_map::TYPE_FRACTION) {
+ *out_msg << " fraction";
}
- if (attr->type_mask & android::ResTable_map::TYPE_INTEGER) {
- *msg << " integer";
+ if (attr.type_mask & android::ResTable_map::TYPE_INTEGER) {
+ *out_msg << " integer";
}
- if (attr->type_mask & android::ResTable_map::TYPE_REFERENCE) {
- *msg << " reference";
+ if (attr.type_mask & android::ResTable_map::TYPE_REFERENCE) {
+ *out_msg << " reference";
}
- if (attr->type_mask & android::ResTable_map::TYPE_STRING) {
- *msg << " string";
+ if (attr.type_mask & android::ResTable_map::TYPE_STRING) {
+ *out_msg << " string";
}
- *msg << " but got " << *value;
+ *out_msg << " but got " << value;
}
-bool Attribute::Matches(const Item* item, DiagMessage* out_msg) const {
+bool Attribute::Matches(const Item& item, DiagMessage* out_msg) const {
+ constexpr const uint32_t TYPE_ENUM = android::ResTable_map::TYPE_ENUM;
+ constexpr const uint32_t TYPE_FLAGS = android::ResTable_map::TYPE_FLAGS;
+ constexpr const uint32_t TYPE_INTEGER = android::ResTable_map::TYPE_INTEGER;
+ constexpr const uint32_t TYPE_REFERENCE = android::ResTable_map::TYPE_REFERENCE;
+
android::Res_value val = {};
- item->Flatten(&val);
+ item.Flatten(&val);
+
+ const uint32_t flattened_data = util::DeviceToHost32(val.data);
// Always allow references.
- const uint32_t mask = type_mask | android::ResTable_map::TYPE_REFERENCE;
- if (!(mask & ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType))) {
+ const uint32_t actual_type = ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType);
+
+ // Only one type must match between the actual and expected.
+ if ((actual_type & (type_mask | TYPE_REFERENCE)) == 0) {
if (out_msg) {
- BuildAttributeMismatchMessage(out_msg, this, item);
+ BuildAttributeMismatchMessage(*this, item, out_msg);
}
return false;
+ }
- } else if (ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType) &
- android::ResTable_map::TYPE_INTEGER) {
- if (static_cast<int32_t>(util::DeviceToHost32(val.data)) < min_int) {
+ // Enums and flags are encoded as integers, so check them first before doing any range checks.
+ if ((type_mask & TYPE_ENUM) != 0 && (actual_type & TYPE_ENUM) != 0) {
+ for (const Symbol& s : symbols) {
+ if (flattened_data == s.value) {
+ return true;
+ }
+ }
+
+ // If the attribute accepts integers, we can't fail here.
+ if ((type_mask & TYPE_INTEGER) == 0) {
if (out_msg) {
- *out_msg << *item << " is less than minimum integer " << min_int;
+ *out_msg << item << " is not a valid enum";
}
return false;
- } else if (static_cast<int32_t>(util::DeviceToHost32(val.data)) > max_int) {
+ }
+ }
+
+ if ((type_mask & TYPE_FLAGS) != 0 && (actual_type & TYPE_FLAGS) != 0) {
+ uint32_t mask = 0u;
+ for (const Symbol& s : symbols) {
+ mask |= s.value;
+ }
+
+ // Check if the flattened data is covered by the flag bit mask.
+ // If the attribute accepts integers, we can't fail here.
+ if ((mask & flattened_data) == flattened_data) {
+ return true;
+ } else if ((type_mask & TYPE_INTEGER) == 0) {
if (out_msg) {
- *out_msg << *item << " is greater than maximum integer " << max_int;
+ *out_msg << item << " is not a valid flag";
+ }
+ return false;
+ }
+ }
+
+ // Finally check the integer range of the value.
+ if ((type_mask & TYPE_INTEGER) != 0 && (actual_type & TYPE_INTEGER) != 0) {
+ if (static_cast<int32_t>(flattened_data) < min_int) {
+ if (out_msg) {
+ *out_msg << item << " is less than minimum integer " << min_int;
+ }
+ return false;
+ } else if (static_cast<int32_t>(flattened_data) > max_int) {
+ if (out_msg) {
+ *out_msg << item << " is greater than maximum integer " << max_int;
}
return false;
}