This is an automated email from the git hooks/post-receive script.
git pushed a commit to reference refs/pull/114/head
in repository efl.
View the commit online.
commit 43811d36ca82174c8de9e7f8ab1f176e3d04e269
Author: Cedric BAIL <[email protected]>
AuthorDate: Sat Aug 15 12:07:37 2026 -0600
fix(evas): never evict the cache entry just inserted
_generic_cache_trim() walks from the least-recently-used end, and the
entry just stored sits at the head. With one entry cached the head is
also the tail, so a budget smaller than a single surface made the trim
free the very surface generic_cache_data_set() had been handed and that
the caller was about to draw with. The next eng_image_draw() then ran on
freed memory and segfaulted.
Reachable through EVAS_SURFACE_CACHE_SIZE, and by extension on any
system whose vector surfaces are larger than the budget. It came in with
the byte-budget cache; the entry-count cache it replaced could not hit
it, since fifty entries were always allowed.
Stop at the head. A budget has to be advisory when a single item does
not fit - holding one surface over budget is correct, freeing it under
the caller is not.
Found by asking what happens when the VG pass leaves canvas draws queued
for longer, which is what turned an intermittent failure into a
reproducible one. The queueing turned out not to be the cause, but the
question was the right one.
The new test provokes it directly, with a 16 KB budget that no real
configuration would use, because at any sane budget this is invisible.
It segfaults without the fix and passes with it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
src/lib/evas/common/evas_common_generic_cache.c | 12 +++++++++++
src/tests/evas/evas_test_vg.c | 27 +++++++++++++++++++++++++
2 files changed, 39 insertions(+)
diff --git a/src/lib/evas/common/evas_common_generic_cache.c b/src/lib/evas/common/evas_common_generic_cache.c
index d17245440e..1dac897afc 100644
--- a/src/lib/evas/common/evas_common_generic_cache.c
+++ b/src/lib/evas/common/evas_common_generic_cache.c
@@ -46,6 +46,18 @@ _generic_cache_trim(Generic_Cache *cache)
}
else if (count <= 50) break;
+ /* Never evict the entry just inserted. It is at the head, and when
+ * the budget is smaller than a single surface the head is also the
+ * tail - trimming it would free the very data the caller stored a
+ * moment ago and is about to draw with. A budget is advisory when
+ * one item does not fit; a use-after-free is not. */
+ /* Never evict the entry just inserted. It is at the head, and when
+ * the budget is smaller than a single surface the head is also the
+ * tail - trimming it would free the very data the caller stored a
+ * moment ago and is about to draw with. A budget is advisory when
+ * one item does not fit; a use-after-free is not. */
+ if (l == cache->lru_list) break;
+
prev = eina_list_prev(l);
if (!entry || entry->ref > 1) continue;
diff --git a/src/tests/evas/evas_test_vg.c b/src/tests/evas/evas_test_vg.c
index 5cb11c9841..10c36aea89 100644
--- a/src/tests/evas/evas_test_vg.c
+++ b/src/tests/evas/evas_test_vg.c
@@ -179,8 +179,35 @@ EFL_START_TEST(evas_vg_container_alpha_is_applied)
}
EFL_END_TEST
+/* A surface cache too small for even one vector surface must still hand back
+ * something usable. The budget is advisory - the entry just stored is at the
+ * head of the LRU and, when nothing else is cached, is also its tail, so a
+ * trim that walks from the tail can otherwise free the surface the caller is
+ * about to draw with. That is invisible at any sane budget, which is why it
+ * is provoked here rather than left to chance. */
+EFL_START_TEST(evas_vg_tiny_surface_cache)
+{
+ const char **eng;
+ char prev[64] = "";
+ const char *old_env = getenv("EVAS_SURFACE_CACHE_SIZE");
+
+ if (old_env) snprintf(prev, sizeof(prev), "%s", old_env);
+ setenv("EVAS_SURFACE_CACHE_SIZE", "16", 1); /* 16 KB: smaller than one surface */
+
+ for (eng = _engines; *eng; eng++)
+ {
+ unsigned int px = 0;
+ _scene_sample(*eng, _build_narrow_then_wide, 4, 128, 128, &px);
+ }
+
+ if (prev[0]) setenv("EVAS_SURFACE_CACHE_SIZE", prev, 1);
+ else unsetenv("EVAS_SURFACE_CACHE_SIZE");
+}
+EFL_END_TEST
+
void evas_test_vg(TCase *tc)
{
tcase_add_test(tc, evas_vg_mixed_sizes_group_opacity);
tcase_add_test(tc, evas_vg_container_alpha_is_applied);
+ tcase_add_test(tc, evas_vg_tiny_surface_cache);
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.