From 74c6a6fe5dc080af33c7516edd05e388a4ce46e2 Mon Sep 17 00:00:00 2001 From: liu weikai Date: Sat, 11 Jul 2026 12:19:11 +0800 Subject: [PATCH] Fix ESP font hot-path stack overflow --- apps/esp32_lvgl/CMakeLists.txt | 8 ++ ...sp32_lvgl_font_hot_path_contract_smoke.cpp | 89 +++++++++++++++++++ .../src/ui/i18n/resource_pack_registry.cpp | 29 ++++-- 3 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 apps/esp32_lvgl/tests/esp32_lvgl_font_hot_path_contract_smoke.cpp diff --git a/apps/esp32_lvgl/CMakeLists.txt b/apps/esp32_lvgl/CMakeLists.txt index 9e969be5..1d654545 100644 --- a/apps/esp32_lvgl/CMakeLists.txt +++ b/apps/esp32_lvgl/CMakeLists.txt @@ -68,6 +68,14 @@ if(BUILD_TESTING) COMMAND trailmate_esp32_lvgl_sd_coredump_contract_smoke "${TRAIL_MATE_REPO_ROOT}") + add_executable(trailmate_esp32_lvgl_font_hot_path_contract_smoke + tests/esp32_lvgl_font_hot_path_contract_smoke.cpp) + target_compile_features(trailmate_esp32_lvgl_font_hot_path_contract_smoke + PRIVATE cxx_std_17) + add_test(NAME trailmate_esp32_lvgl_font_hot_path_contract_smoke + COMMAND trailmate_esp32_lvgl_font_hot_path_contract_smoke + "${TRAIL_MATE_REPO_ROOT}") + add_executable(trailmate_product_target_profile_smoke "${TRAIL_MATE_REPO_ROOT}/modules/product_composition/tests/test_target_profile.cpp") target_link_libraries(trailmate_product_target_profile_smoke diff --git a/apps/esp32_lvgl/tests/esp32_lvgl_font_hot_path_contract_smoke.cpp b/apps/esp32_lvgl/tests/esp32_lvgl_font_hot_path_contract_smoke.cpp new file mode 100644 index 00000000..0f9e5cf1 --- /dev/null +++ b/apps/esp32_lvgl/tests/esp32_lvgl_font_hot_path_contract_smoke.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include + +namespace +{ + +std::string read_file(const std::filesystem::path& path) +{ + std::ifstream stream(path, std::ios::binary); + assert(stream.is_open()); + std::ostringstream out; + out << stream.rdbuf(); + return out.str(); +} + +bool contains(const std::string& haystack, const char* needle) +{ + return haystack.find(needle) != std::string::npos; +} + +bool not_contains(const std::string& haystack, const char* needle) +{ + return !contains(haystack, needle); +} + +std::size_t position_of(const std::string& haystack, const char* needle) +{ + const auto pos = haystack.find(needle); + assert(pos != std::string::npos); + return pos; +} + +std::size_t position_of_after(const std::string& haystack, + const char* needle, + std::size_t offset) +{ + const auto pos = haystack.find(needle, offset); + assert(pos != std::string::npos); + return pos; +} + +} // namespace + +int main(int argc, char** argv) +{ + assert(argc == 2); + const std::filesystem::path repo_root = argv[1]; + + const std::string registry = read_file( + repo_root / "modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp"); + + assert(contains(registry, "bool s_defer_font_load_overlay_present = false;")); + assert(contains(registry, "font_load_overlay_policy()")); + assert(contains(registry, "Policy::Overlay")); + assert(contains(registry, "Policy::OverlayImmediate")); + assert(contains(registry, "font_load_overlay_policy(),")); + + const std::size_t forced_overlay = position_of(registry, "class ScopedForcedFontLoadOverlay"); + const std::size_t defer_restore = position_of(registry, "previous_defer_present_"); + const std::size_t defer_enable = position_of(registry, "s_defer_font_load_overlay_present = true;"); + assert(forced_overlay < defer_restore); + assert(defer_restore < defer_enable); + + const std::size_t hot_path_guard = position_of(registry, "bool can_activate_content_supplement_for_text"); + const std::size_t hot_path_check = position_of(registry, "can_load_font_from_content_hot_path(pack)"); + const std::size_t budget_check = position_of(registry, "can_add_content_supplement(pack)"); + assert(hot_path_guard < hot_path_check); + assert(hot_path_check < budget_check); + + const std::size_t content_ensure = position_of(registry, "bool ensure_content_font_for_text"); + const std::size_t supplement_check = position_of( + registry, + "!can_activate_content_supplement_for_text(*candidate)"); + const std::size_t hot_path_reason = position_of_after(registry, "\"ui_hot_path\"", supplement_check); + const std::size_t content_budget_reason = position_of_after(registry, "\"content_budget\"", supplement_check); + assert(content_ensure < supplement_check); + assert(supplement_check < hot_path_reason); + assert(supplement_check < content_budget_reason); + + assert(not_contains( + registry, + "foreground::make_snapshot(foreground::Slot::I18nFontLoad,\n" + " foreground::Policy::OverlayImmediate")); + + return 0; +} diff --git a/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp b/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp index ce472db4..5adb462c 100644 --- a/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp +++ b/modules/ui_shared/src/ui/i18n/resource_pack_registry.cpp @@ -191,6 +191,7 @@ unsigned s_ui_helper_route_diagnostics = 0; unsigned s_direct_route_diagnostics = 0; bool s_allow_sync_external_font_activation = false; bool s_force_font_load_overlay = false; +bool s_defer_font_load_overlay_present = false; std::uint32_t s_font_load_overlay_generation = 0; std::uint32_t next_font_load_overlay_generation() @@ -203,6 +204,13 @@ std::uint32_t next_font_load_overlay_generation() return s_font_load_overlay_generation; } +::ui::widgets::foreground_operation::Policy font_load_overlay_policy() +{ + return s_defer_font_load_overlay_present + ? ::ui::widgets::foreground_operation::Policy::Overlay + : ::ui::widgets::foreground_operation::Policy::OverlayImmediate; +} + class ScopedFontLoadOverlay { public: @@ -218,7 +226,7 @@ class ScopedFontLoadOverlay namespace foreground = ::ui::widgets::foreground_operation; foreground::publish( foreground::make_snapshot(foreground::Slot::I18nFontLoad, - foreground::Policy::OverlayImmediate, + font_load_overlay_policy(), foreground::Priority::Blocking, "Loading language pack...", pack.display_name.empty() @@ -330,14 +338,17 @@ class ScopedForcedFontLoadOverlay { public: ScopedForcedFontLoadOverlay() - : previous_force_overlay_(s_force_font_load_overlay) + : previous_force_overlay_(s_force_font_load_overlay), + previous_defer_present_(s_defer_font_load_overlay_present) { s_force_font_load_overlay = true; + s_defer_font_load_overlay_present = true; } ~ScopedForcedFontLoadOverlay() { s_force_font_load_overlay = previous_force_overlay_; + s_defer_font_load_overlay_present = previous_defer_present_; } ScopedForcedFontLoadOverlay(const ScopedForcedFontLoadOverlay&) = delete; @@ -345,6 +356,7 @@ class ScopedForcedFontLoadOverlay private: bool previous_force_overlay_ = false; + bool previous_defer_present_ = false; }; const char* safe_text(const char* value) @@ -1616,11 +1628,14 @@ bool can_activate_content_supplement_for_text(const FontPackRecord& pack) // Content text is data from contacts, chat, map labels, and extension pages. // It must not be tied to the display locale: an English UI can still need a // Chinese/Korean/emoji content font when those optional packs are installed. - // The guard here is coverage + memory-profile budget, while load_font_pack() - // still owns failure backoff so a missing/bad pack is not retried per label. + // The guard here is hot-path safety + coverage + memory-profile budget, + // while load_font_pack() still owns failure backoff so a missing/bad pack is + // not retried per label. return pack.builtin || is_font_runtime_loaded(pack) || - (font_pack_supports_content(pack) && can_add_content_supplement(pack)); + (can_load_font_from_content_hot_path(pack) && + font_pack_supports_content(pack) && + can_add_content_supplement(pack)); #else (void)pack; return true; @@ -3331,7 +3346,9 @@ bool ensure_content_font_for_text(const char* text) !kAllowSynchronousContentSupplementFontLoad && !can_activate_content_supplement_for_text(*candidate)) { - log_font_load_deferred(*candidate, "content_supplement", "content_budget"); + const char* reason = + can_load_font_from_content_hot_path(*candidate) ? "content_budget" : "ui_hot_path"; + log_font_load_deferred(*candidate, "content_supplement", reason); break; } if (!ensure_font_pack_loaded(candidate))