From c75c2e092218a7d77be39c89bfba7dd2b4823ac1 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 17 Aug 2020 08:42:48 -0700 Subject: libandroidfw hardening for IncFs Migrate libandroifw to using incfs::util::map_ptr to prevent processes from crashing when parsing the resources.arsc, parsing compiled xml, files, and retrieving resource values. This change propagates incremental failures to the JNI level where they are raised as ResourcesNotFoundException. Performance of ResourcesPerfWorkloads without change (time in nanoseconds): [1/3] com.android.resources.perf.PerfTest#youtube: PASSED (11.883s) youtube_ns_median: 93812805 youtube_ns_standardDeviation: 4387062 youtube_ns_mean: 94455597 [2/3] com.android.resources.perf.PerfTest#maps: PASSED (11.265s) maps_ns_standardDeviation: 2997543 maps_ns_mean: 83480371 maps_ns_median: 82210941 [3/3] com.android.resources.perf.PerfTest#gmail: PASSED (24.963s) gmail_ns_median: 266141091 gmail_ns_standardDeviation: 3492043 gmail_ns_mean: 267472765 With change and verification forcibly enabled for all apks (including the framework-res.apk): [1/3] com.android.resources.perf.PerfTest#youtube: PASSED (11.646s) youtube_ns_median: 101999396 youtube_ns_standardDeviation: 4625782 youtube_ns_mean: 102631770 [2/3] com.android.resources.perf.PerfTest#maps: PASSED (11.286s) maps_ns_standardDeviation: 2692088 maps_ns_mean: 91326538 maps_ns_median: 90519884 [3/3] com.android.resources.perf.PerfTest#gmail: PASSED (24.694s) gmail_ns_median: 290284442 gmail_ns_standardDeviation: 5764632 gmail_ns_mean: 291660464 With change and verification disabled: [1/3] com.android.resources.perf.PerfTest#youtube: PASSED (11.748s) youtube_ns_median: 95490747 youtube_ns_standardDeviation: 7282249 youtube_ns_mean: 98442515 [2/3] com.android.resources.perf.PerfTest#maps: PASSED (10.862s) maps_ns_standardDeviation: 4484213 maps_ns_mean: 87912988 maps_ns_median: 86325549 [3/3] com.android.resources.perf.PerfTest#gmail: PASSED (24.034s) gmail_ns_median: 282175838 gmail_ns_standardDeviation: 6560876 gmail_ns_mean: 282869146 These tests were done on a Pixel 3 and with cpu settings configured by libs/hwui/tests/scripts/prep_generic.sh: Locked CPUs 4,5,6,7 to 1459200 / 2803200 KHz Disabled CPUs 0,1,2,3 Bug: 160635104 Bug: 169423204 Test: boot device && atest ResourcesPerfWorkloads Change-Id: I5cd1bc8a2257bffaba6ca4a1c96f4e6640106866 --- libs/androidfw/AttributeResolution.cpp | 447 ++++++++++++++------------------- 1 file changed, 195 insertions(+), 252 deletions(-) (limited to 'libs/androidfw/AttributeResolution.cpp') diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp index e62fb614e195..71919fdbb736 100644 --- a/libs/androidfw/AttributeResolution.cpp +++ b/libs/androidfw/AttributeResolution.cpp @@ -24,9 +24,12 @@ #include "androidfw/AttributeFinder.h" constexpr bool kDebugStyles = false; +#define DEBUG_LOG(...) do { if (kDebugStyles) { ALOGI(__VA_ARGS__); } } while(0) namespace android { +namespace { + // Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0. static uint32_t ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) { return cookie != kInvalidCookie ? static_cast(cookie + 1) : static_cast(-1); @@ -61,136 +64,149 @@ class BagAttributeFinder } }; -bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res, - uint32_t* src_values, size_t src_values_length, uint32_t* attrs, - size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { - if (kDebugStyles) { - ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme, - def_style_attr, def_style_res); +base::expected GetStyleBag(Theme* theme, + uint32_t theme_attribute_resid, + uint32_t fallback_resid, + uint32_t* out_theme_flags) { + // Load the style from the attribute if specified. + if (theme_attribute_resid != 0U) { + std::optional value = theme->GetAttribute(theme_attribute_resid); + if (value.has_value()) { + *out_theme_flags |= value->flags; + auto result = theme->GetAssetManager()->ResolveBag(*value); + if (result.has_value() || IsIOError(result)) { + return result; + } + } } - AssetManager2* assetmanager = theme->GetAssetManager(); - ResTable_config config; - Res_value value; + // Fallback to loading the style from the resource id if specified. + if (fallback_resid != 0U) { + return theme->GetAssetManager()->GetBag(fallback_resid); + } - int indices_idx = 0; + return base::unexpected(std::nullopt); +} - // Load default style from attribute, if specified... - uint32_t def_style_flags = 0u; - if (def_style_attr != 0) { - Res_value value; - if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) { - if (value.dataType == Res_value::TYPE_REFERENCE) { - def_style_res = value.data; - } - } +base::expected GetXmlStyleBag(Theme* theme, + ResXMLParser* xml_parser, + uint32_t* out_theme_flags) { + if (xml_parser == nullptr) { + return base::unexpected(std::nullopt); } - // Retrieve the default style bag, if requested. - const ResolvedBag* default_style_bag = nullptr; - if (def_style_res != 0) { - default_style_bag = assetmanager->GetBag(def_style_res); - if (default_style_bag != nullptr) { - def_style_flags |= default_style_bag->type_spec_flags; + // Retrieve the style resource ID associated with the current XML tag's style attribute. + Res_value value; + const ssize_t idx = xml_parser->indexOfStyle(); + if (idx < 0 || xml_parser->getAttributeValue(idx, &value) < 0) { + return base::unexpected(std::nullopt); + } + + if (value.dataType == Res_value::TYPE_ATTRIBUTE) { + // Resolve the attribute with out theme. + if (std::optional result = theme->GetAttribute(value.data)) { + *out_theme_flags |= result->flags; + return theme->GetAssetManager()->ResolveBag(*result); } } - BagAttributeFinder def_style_attr_finder(default_style_bag); + if (value.dataType == Res_value::TYPE_REFERENCE) { + return theme->GetAssetManager()->GetBag(value.data); + } + + return base::unexpected(std::nullopt); +} + +} // namespace + +base::expected ResolveAttrs(Theme* theme, uint32_t def_style_attr, + uint32_t def_style_res, uint32_t* src_values, + size_t src_values_length, uint32_t* attrs, + size_t attrs_length, uint32_t* out_values, + uint32_t* out_indices) { + DEBUG_LOG("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme, def_style_attr, + def_style_res); + + int indices_idx = 0; + const AssetManager2* assetmanager = theme->GetAssetManager(); + + // Load default style from attribute or resource id, if specified... + uint32_t def_style_theme_flags = 0U; + const auto default_style_bag = GetStyleBag(theme, def_style_attr, def_style_res, + &def_style_theme_flags); + if (UNLIKELY(IsIOError(default_style_bag))) { + return base::unexpected(GetIOError(default_style_bag.error())); + } + + BagAttributeFinder def_style_attr_finder(default_style_bag.value_or(nullptr)); // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. for (size_t ii = 0; ii < attrs_length; ii++) { const uint32_t cur_ident = attrs[ii]; - - if (kDebugStyles) { - ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident); - } - - ApkAssetsCookie cookie = kInvalidCookie; - uint32_t type_set_flags = 0; - - value.dataType = Res_value::TYPE_NULL; - value.data = Res_value::DATA_NULL_UNDEFINED; - config.density = 0; + DEBUG_LOG("RETRIEVING ATTR 0x%08x...", cur_ident); // Try to find a value for this attribute... we prioritize values // coming from, first XML attributes, then XML style, then default // style, and finally the theme. // Retrieve the current input value if available. + AssetManager2::SelectedValue value{}; if (src_values_length > 0 && src_values[ii] != 0) { - value.dataType = Res_value::TYPE_ATTRIBUTE; + value.type = Res_value::TYPE_ATTRIBUTE; value.data = src_values[ii]; - if (kDebugStyles) { - ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data); - } + DEBUG_LOG("-> From values: type=0x%x, data=0x%08x", value.type, value.data); } else { const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident); if (entry != def_style_attr_finder.end()) { - cookie = entry->cookie; - type_set_flags = def_style_flags; - value = entry->value; - if (kDebugStyles) { - ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); - } + value = AssetManager2::SelectedValue(*default_style_bag, *entry); + value.flags |= def_style_theme_flags; + DEBUG_LOG("-> From def style: type=0x%x, data=0x%08x", value.type, value.data); } } - uint32_t resid = 0; - if (value.dataType != Res_value::TYPE_NULL) { + if (value.type != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ApkAssetsCookie new_cookie = - theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid); - if (new_cookie != kInvalidCookie) { - cookie = new_cookie; - } - if (kDebugStyles) { - ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data); + const auto result = theme->ResolveAttributeReference(value); + if (UNLIKELY(IsIOError(result))) { + return base::unexpected(GetIOError(result.error())); } + DEBUG_LOG("-> Resolved attr: type=0x%x, data=0x%08x", value.type, value.data); } else if (value.data != Res_value::DATA_NULL_EMPTY) { // If we still don't have a value for this attribute, try to find it in the theme! - ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); - if (new_cookie != kInvalidCookie) { - if (kDebugStyles) { - ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); - } - new_cookie = - assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid); - if (new_cookie != kInvalidCookie) { - cookie = new_cookie; - } - if (kDebugStyles) { - ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data); + if (auto attr_value = theme->GetAttribute(cur_ident)) { + value = *attr_value; + DEBUG_LOG("-> From theme: type=0x%x, data=0x%08x", value.type, value.data); + + const auto result = assetmanager->ResolveReference(value); + if (UNLIKELY(IsIOError(result))) { + return base::unexpected(GetIOError(result.error())); } + DEBUG_LOG("-> Resolved theme: type=0x%x, data=0x%08x", value.type, value.data); } } // Deal with the special @null value -- it turns back to TYPE_NULL. - if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { - if (kDebugStyles) { - ALOGI("-> Setting to @null!"); - } - value.dataType = Res_value::TYPE_NULL; + if (value.type == Res_value::TYPE_REFERENCE && value.data == 0) { + DEBUG_LOG("-> Setting to @null!"); + value.type = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - cookie = kInvalidCookie; + value.cookie = kInvalidCookie; } - if (kDebugStyles) { - ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.dataType, value.data); - } + DEBUG_LOG("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.type, value.data); // Write the final value back to Java. - out_values[STYLE_TYPE] = value.dataType; + out_values[STYLE_TYPE] = value.type; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); - out_values[STYLE_RESOURCE_ID] = resid; - out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; - out_values[STYLE_DENSITY] = config.density; + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(value.cookie); + out_values[STYLE_RESOURCE_ID] = value.resid; + out_values[STYLE_CHANGING_CONFIGURATIONS] = value.flags; + out_values[STYLE_DENSITY] = value.config.density; if (out_indices != nullptr && - (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY)) { - indices_idx++; - out_indices[indices_idx] = ii; + (value.type != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY)) { + out_indices[++indices_idx] = ii; } out_values += STYLE_NUM_ENTRIES; @@ -199,93 +215,46 @@ bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res, if (out_indices != nullptr) { out_indices[0] = indices_idx; } - return true; + return {}; } -void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, - uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length, - uint32_t* out_values, uint32_t* out_indices) { - if (kDebugStyles) { - ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme, - def_style_attr, def_style_resid, xml_parser); - } - - AssetManager2* assetmanager = theme->GetAssetManager(); - ResTable_config config; - Res_value value; +base::expected ApplyStyle(Theme* theme, ResXMLParser* xml_parser, + uint32_t def_style_attr, + uint32_t def_style_resid, + const uint32_t* attrs, size_t attrs_length, + uint32_t* out_values, uint32_t* out_indices) { + DEBUG_LOG("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme, + def_style_attr, def_style_resid, xml_parser); int indices_idx = 0; + const AssetManager2* assetmanager = theme->GetAssetManager(); // Load default style from attribute, if specified... - uint32_t def_style_flags = 0u; - if (def_style_attr != 0) { - Res_value value; - if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) { - if (value.dataType == Res_value::TYPE_REFERENCE) { - def_style_resid = value.data; - } - } + uint32_t def_style_theme_flags = 0U; + const auto default_style_bag = GetStyleBag(theme, def_style_attr, def_style_resid, + &def_style_theme_flags); + if (IsIOError(default_style_bag)) { + return base::unexpected(GetIOError(default_style_bag.error())); } // Retrieve the style resource ID associated with the current XML tag's style attribute. - uint32_t style_resid = 0u; - uint32_t style_flags = 0u; - if (xml_parser != nullptr) { - ssize_t idx = xml_parser->indexOfStyle(); - if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) { - if (value.dataType == value.TYPE_ATTRIBUTE) { - // Resolve the attribute with out theme. - if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) { - value.dataType = Res_value::TYPE_NULL; - } - } - - if (value.dataType == value.TYPE_REFERENCE) { - style_resid = value.data; - } - } - } - - // Retrieve the default style bag, if requested. - const ResolvedBag* default_style_bag = nullptr; - if (def_style_resid != 0) { - default_style_bag = assetmanager->GetBag(def_style_resid); - if (default_style_bag != nullptr) { - def_style_flags |= default_style_bag->type_spec_flags; - } + uint32_t xml_style_theme_flags = 0U; + const auto xml_style_bag = GetXmlStyleBag(theme, xml_parser, &def_style_theme_flags); + if (IsIOError(xml_style_bag)) { + return base::unexpected(GetIOError(xml_style_bag.error())); } - BagAttributeFinder def_style_attr_finder(default_style_bag); - - // Retrieve the style class bag, if requested. - const ResolvedBag* xml_style_bag = nullptr; - if (style_resid != 0) { - xml_style_bag = assetmanager->GetBag(style_resid); - if (xml_style_bag != nullptr) { - style_flags |= xml_style_bag->type_spec_flags; - } - } - - BagAttributeFinder xml_style_attr_finder(xml_style_bag); - - // Retrieve the XML attributes, if requested. + BagAttributeFinder def_style_attr_finder(default_style_bag.value_or(nullptr)); + BagAttributeFinder xml_style_attr_finder(xml_style_bag.value_or(nullptr)); XmlAttributeFinder xml_attr_finder(xml_parser); // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. for (size_t ii = 0; ii < attrs_length; ii++) { const uint32_t cur_ident = attrs[ii]; + DEBUG_LOG("RETRIEVING ATTR 0x%08x...", cur_ident); - if (kDebugStyles) { - ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident); - } - - ApkAssetsCookie cookie = kInvalidCookie; - uint32_t type_set_flags = 0u; - - value.dataType = Res_value::TYPE_NULL; - value.data = Res_value::DATA_NULL_UNDEFINED; - config.density = 0; + AssetManager2::SelectedValue value{}; uint32_t value_source_resid = 0; // Try to find a value for this attribute... we prioritize values @@ -296,178 +265,152 @@ void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident); if (xml_attr_idx != xml_attr_finder.end()) { // We found the attribute we were looking for. - xml_parser->getAttributeValue(xml_attr_idx, &value); - if (kDebugStyles) { - ALOGI("-> From XML: type=0x%x, data=0x%08x", value.dataType, value.data); - } + Res_value attribute_value; + xml_parser->getAttributeValue(xml_attr_idx, &attribute_value); + value.type = attribute_value.dataType; + value.data = attribute_value.data; value_source_resid = xml_parser->getSourceResourceId(); + DEBUG_LOG("-> From XML: type=0x%x, data=0x%08x", value.type, value.data); } - if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { + if (value.type == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { // Walk through the style class values looking for the requested attribute. const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident); if (entry != xml_style_attr_finder.end()) { - // We found the attribute we were looking for. - cookie = entry->cookie; - type_set_flags = style_flags; - value = entry->value; + value = AssetManager2::SelectedValue(*xml_style_bag, *entry); + value.flags |= xml_style_theme_flags; value_source_resid = entry->style; - if (kDebugStyles) { - ALOGI("-> From style: type=0x%x, data=0x%08x, style=0x%08x", value.dataType, value.data, - entry->style); - } + DEBUG_LOG("-> From style: type=0x%x, data=0x%08x, style=0x%08x", value.type, value.data, + value_source_resid); } } - if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { + if (value.type == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { // Walk through the default style values looking for the requested attribute. const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident); if (entry != def_style_attr_finder.end()) { - // We found the attribute we were looking for. - cookie = entry->cookie; - type_set_flags = def_style_flags; - value = entry->value; - if (kDebugStyles) { - ALOGI("-> From def style: type=0x%x, data=0x%08x, style=0x%08x", value.dataType, value.data, - entry->style); - } + value = AssetManager2::SelectedValue(*default_style_bag, *entry); + value.flags |= def_style_theme_flags; value_source_resid = entry->style; + DEBUG_LOG("-> From def style: type=0x%x, data=0x%08x, style=0x%08x", value.type, value.data, + entry->style); } } - uint32_t resid = 0u; - if (value.dataType != Res_value::TYPE_NULL) { + if (value.type != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ApkAssetsCookie new_cookie = - theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid); - if (new_cookie != kInvalidCookie) { - cookie = new_cookie; - } - - if (kDebugStyles) { - ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data); + auto result = theme->ResolveAttributeReference(value); + if (UNLIKELY(IsIOError(result))) { + return base::unexpected(GetIOError(result.error())); } + DEBUG_LOG("-> Resolved attr: type=0x%x, data=0x%08x", value.type, value.data); } else if (value.data != Res_value::DATA_NULL_EMPTY) { // If we still don't have a value for this attribute, try to find it in the theme! - ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); - // TODO: set value_source_resid for the style in the theme that was used. - if (new_cookie != kInvalidCookie) { - if (kDebugStyles) { - ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); - } - new_cookie = - assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid); - if (new_cookie != kInvalidCookie) { - cookie = new_cookie; - } + if (auto attr_value = theme->GetAttribute(cur_ident)) { + value = *attr_value; + DEBUG_LOG("-> From theme: type=0x%x, data=0x%08x", value.type, value.data); - if (kDebugStyles) { - ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data); + auto result = assetmanager->ResolveReference(value); + if (UNLIKELY(IsIOError(result))) { + return base::unexpected(GetIOError(result.error())); } + DEBUG_LOG("-> Resolved theme: type=0x%x, data=0x%08x", value.type, value.data); + // TODO: set value_source_resid for the style in the theme that was used. } } // Deal with the special @null value -- it turns back to TYPE_NULL. - if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { - if (kDebugStyles) { - ALOGI("-> Setting to @null!"); - } - value.dataType = Res_value::TYPE_NULL; + if (value.type == Res_value::TYPE_REFERENCE && value.data == 0U) { + DEBUG_LOG("-> Setting to @null!"); + value.type = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - cookie = kInvalidCookie; + value.cookie = kInvalidCookie; } - if (kDebugStyles) { - ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.dataType, value.data); - } + DEBUG_LOG("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.type, value.data); // Write the final value back to Java. - out_values[STYLE_TYPE] = value.dataType; + out_values[STYLE_TYPE] = value.type; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); - out_values[STYLE_RESOURCE_ID] = resid; - out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; - out_values[STYLE_DENSITY] = config.density; + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(value.cookie); + out_values[STYLE_RESOURCE_ID] = value.resid; + out_values[STYLE_CHANGING_CONFIGURATIONS] = value.flags; + out_values[STYLE_DENSITY] = value.config.density; out_values[STYLE_SOURCE_RESOURCE_ID] = value_source_resid; - if (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY) { - indices_idx++; - + if (value.type != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY) { // out_indices must NOT be nullptr. - out_indices[indices_idx] = ii; + out_indices[++indices_idx] = ii; } out_values += STYLE_NUM_ENTRIES; } // out_indices must NOT be nullptr. out_indices[0] = indices_idx; + return {}; } -bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs, - size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { - ResTable_config config; - Res_value value; - +base::expected RetrieveAttributes(AssetManager2* assetmanager, + ResXMLParser* xml_parser, + uint32_t* attrs, + size_t attrs_length, + uint32_t* out_values, + uint32_t* out_indices) { int indices_idx = 0; // Retrieve the XML attributes, if requested. - const size_t xml_attr_count = xml_parser->getAttributeCount(); size_t ix = 0; + const size_t xml_attr_count = xml_parser->getAttributeCount(); uint32_t cur_xml_attr = xml_parser->getAttributeNameResID(ix); // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. for (size_t ii = 0; ii < attrs_length; ii++) { const uint32_t cur_ident = attrs[ii]; - ApkAssetsCookie cookie = kInvalidCookie; - uint32_t type_set_flags = 0u; - - value.dataType = Res_value::TYPE_NULL; - value.data = Res_value::DATA_NULL_UNDEFINED; - config.density = 0; + AssetManager2::SelectedValue value{}; // Try to find a value for this attribute... // Skip through XML attributes until the end or the next possible match. while (ix < xml_attr_count && cur_ident > cur_xml_attr) { - ix++; - cur_xml_attr = xml_parser->getAttributeNameResID(ix); + cur_xml_attr = xml_parser->getAttributeNameResID(++ix); } + // Retrieve the current XML attribute if it matches, and step to next. if (ix < xml_attr_count && cur_ident == cur_xml_attr) { - xml_parser->getAttributeValue(ix, &value); - ix++; - cur_xml_attr = xml_parser->getAttributeNameResID(ix); + Res_value attribute_value; + xml_parser->getAttributeValue(ix, &attribute_value); + value.type = attribute_value.dataType; + value.data = attribute_value.data; + cur_xml_attr = xml_parser->getAttributeNameResID(++ix); } - uint32_t resid = 0u; - if (value.dataType != Res_value::TYPE_NULL) { + if (value.type != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ApkAssetsCookie new_cookie = - assetmanager->ResolveReference(cookie, &value, &config, &type_set_flags, &resid); - if (new_cookie != kInvalidCookie) { - cookie = new_cookie; + auto result = assetmanager->ResolveReference(value); + if (UNLIKELY(IsIOError(result))) { + return base::unexpected(GetIOError(result.error())); } } // Deal with the special @null value -- it turns back to TYPE_NULL. - if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { - value.dataType = Res_value::TYPE_NULL; + if (value.type == Res_value::TYPE_REFERENCE && value.data == 0U) { + value.type = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - cookie = kInvalidCookie; + value.cookie = kInvalidCookie; } // Write the final value back to Java. - out_values[STYLE_TYPE] = value.dataType; + out_values[STYLE_TYPE] = value.type; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); - out_values[STYLE_RESOURCE_ID] = resid; - out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; - out_values[STYLE_DENSITY] = config.density; + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(value.cookie); + out_values[STYLE_RESOURCE_ID] = value.resid; + out_values[STYLE_CHANGING_CONFIGURATIONS] = value.flags; + out_values[STYLE_DENSITY] = value.config.density; if (out_indices != nullptr && - (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY)) { - indices_idx++; - out_indices[indices_idx] = ii; + (value.type != Res_value::TYPE_NULL || + value.data == Res_value::DATA_NULL_EMPTY)) { + out_indices[++indices_idx] = ii; } out_values += STYLE_NUM_ENTRIES; @@ -476,7 +419,7 @@ bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, u if (out_indices != nullptr) { out_indices[0] = indices_idx; } - return true; + return {}; } } // namespace android -- cgit v1.2.3-59-g8ed1b From 87e89546b66da6d5098b46ff9b868ef82a753932 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 5 Oct 2020 14:24:35 -0700 Subject: Cache resolved theme values When calling Resources#obtainStyledAttributes, if a value for an attribute is supplied from the theme, and the value in the theme is a reference to a resource, the reference will be resolved using AssetManager2::ResolveReference each time the value from the theme is selected. This causes Resources#obtainStyledAttributes to do repeated work every time the same attribute is supplied from the theme in multiple invocations. Caching the result of ResolveReference reduces the cost of this repeated work and reduces the amount of time needed to inflate views. Before: com.android.resources.perf (3 Tests) [1/3] com.android.resources.perf.PerfTest#youtube: PASSED (11.748s) youtube_ns_median: 95490747 youtube_ns_standardDeviation: 7282249 youtube_ns_mean: 98442515 [2/3] com.android.resources.perf.PerfTest#maps: PASSED (10.862s) maps_ns_standardDeviation: 4484213 maps_ns_mean: 87912988 maps_ns_median: 86325549 [3/3] com.android.resources.perf.PerfTest#gmail: PASSED (24.034s) gmail_ns_median: 282175838 gmail_ns_standardDeviation: 6560876 gmail_ns_mean: 282869146 After: com.android.resources.perf (3 Tests) [1/3] com.android.resources.perf.PerfTest#youtube: PASSED (11.245s) youtube_ns_median: 92292347 youtube_ns_standardDeviation: 5899906 youtube_ns_mean: 93045239 [2/3] com.android.resources.perf.PerfTest#maps: PASSED (10.583s) maps_ns_standardDeviation: 7567929 maps_ns_mean: 81895979 maps_ns_median: 78647883 [3/3] com.android.resources.perf.PerfTest#gmail: PASSED (21.439s) gmail_ns_median: 229185043 gmail_ns_standardDeviation: 8770133 gmail_ns_mean: 232561234 These tests were done on a Pixel 3 and with cpu settings configured by libs/hwui/tests/scripts/prep_generic.sh: Locked CPUs 4,5,6,7 to 1459200 / 2803200 KHz Disabled CPUs 0,1,2,3 Bug: 170232288 Test: atest ResourcesPerfWorkloads Change-Id: I1e9e9acaa40fa60475a0e55230e11243f5b69b39 --- libs/androidfw/AssetManager2.cpp | 31 +++++++++++++++++++----- libs/androidfw/AttributeResolution.cpp | 7 +++--- libs/androidfw/include/androidfw/AssetManager2.h | 12 ++++++--- 3 files changed, 36 insertions(+), 14 deletions(-) (limited to 'libs/androidfw/AttributeResolution.cpp') diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 3e54dc67db76..d349628c2ab4 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -963,14 +963,25 @@ base::expected AssetManager2::GetRe } base::expected AssetManager2::ResolveReference( - AssetManager2::SelectedValue& value) const { + AssetManager2::SelectedValue& value, bool cache_value) const { if (value.type != Res_value::TYPE_REFERENCE || value.data == 0U) { // Not a reference. Nothing to do. return {}; } - uint32_t combined_flags = value.flags; - uint32_t resolve_resid = value.data; + const uint32_t original_flags = value.flags; + const uint32_t original_resid = value.data; + if (cache_value) { + auto cached_value = cached_resolved_values_.find(value.data); + if (cached_value != cached_resolved_values_.end()) { + value = cached_value->second; + value.flags |= original_flags; + return {}; + } + } + + uint32_t combined_flags = 0U; + uint32_t resolve_resid = original_resid; constexpr const uint32_t kMaxIterations = 20; for (uint32_t i = 0U;; i++) { auto result = GetResource(resolve_resid, true /*may_be_bag*/); @@ -981,9 +992,15 @@ base::expected AssetManager2::ResolveReference( if (result->type != Res_value::TYPE_REFERENCE || result->data == Res_value::DATA_NULL_UNDEFINED || result->data == resolve_resid || i == kMaxIterations) { - // This reference can't be resolved, so exit now and let the caller deal with it. + result->flags |= combined_flags; + if (cache_value) { + cached_resolved_values_[original_resid] = *result; + } + + // Add the original flags after caching the result so queries with a different set of original + // flags do not include these original flags. value = *result; - value.flags |= combined_flags; + value.flags |= original_flags; return {}; } @@ -1353,6 +1370,8 @@ void AssetManager2::InvalidateCaches(uint32_t diff) { ++iter; } } + + cached_resolved_values_.clear(); } uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const { @@ -1533,7 +1552,7 @@ base::expected Theme::ResolveAttributeReference( return base::unexpected(std::nullopt); } - auto resolve_result = asset_manager_->ResolveReference(*result); + auto resolve_result = asset_manager_->ResolveReference(*result, true /* cache_value */); if (resolve_result.has_value()) { result->flags |= value.flags; value = *result; diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp index 71919fdbb736..347b4ec8346c 100644 --- a/libs/androidfw/AttributeResolution.cpp +++ b/libs/androidfw/AttributeResolution.cpp @@ -39,8 +39,7 @@ class XmlAttributeFinder : public BackTrackingAttributeFinder { public: explicit XmlAttributeFinder(const ResXMLParser* parser) - : BackTrackingAttributeFinder( - 0, parser != nullptr ? parser->getAttributeCount() : 0), + : BackTrackingAttributeFinder(0, parser != nullptr ? parser->getAttributeCount() : 0), parser_(parser) {} inline uint32_t GetAttribute(size_t index) const { @@ -178,7 +177,7 @@ base::expected ResolveAttrs(Theme* theme, uint32_t def_ value = *attr_value; DEBUG_LOG("-> From theme: type=0x%x, data=0x%08x", value.type, value.data); - const auto result = assetmanager->ResolveReference(value); + const auto result = assetmanager->ResolveReference(value, true /* cache_value */); if (UNLIKELY(IsIOError(result))) { return base::unexpected(GetIOError(result.error())); } @@ -310,7 +309,7 @@ base::expected ApplyStyle(Theme* theme, ResXMLParser* x value = *attr_value; DEBUG_LOG("-> From theme: type=0x%x, data=0x%08x", value.type, value.data); - auto result = assetmanager->ResolveReference(value); + auto result = assetmanager->ResolveReference(value, true /* cache_value */); if (UNLIKELY(IsIOError(result))) { return base::unexpected(GetIOError(result.error())); } diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 4e993b0838a2..a92694c94b9f 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -264,11 +264,14 @@ class AssetManager2 { // Resolves the resource referenced in `value` if the type is Res_value::TYPE_REFERENCE. // // If the data type is not Res_value::TYPE_REFERENCE, no work is done. Configuration flags of the - // values pointed to by the reference are OR'd into `value.flags`. + // values pointed to by the reference are OR'd into `value.flags`. If `cache_value` is true, then + // the resolved value will be cached and used when attempting to resolve the resource id specified + // in `value`. // // Returns a null error if the resource could not be resolved, or an I/O error if reading // resource data failed. - base::expected ResolveReference(SelectedValue& value) const; + base::expected ResolveReference(SelectedValue& value, + bool cache_value = false) const; // Retrieves the best matching bag/map resource with ID `resid`. // @@ -446,13 +449,14 @@ class AssetManager2 { // a number of times for each view during View inspection. mutable std::unordered_map> cached_bag_resid_stacks_; + // Cached set of resolved resource values. + mutable std::unordered_map cached_resolved_values_; + // Whether or not to save resource resolution steps bool resource_resolution_logging_enabled_ = false; struct Resolution { - struct Step { - enum class Type { INITIAL, BETTER_MATCH, -- cgit v1.2.3-59-g8ed1b