This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch edje-vector-intergration
in repository efl.

View the commit online.

commit 43dd2187bcb603cfe30301bd63ae3eca373050ab
Author: [email protected] <[email protected]>
AuthorDate: Thu Apr 30 21:30:37 2026 -0600

    edje: address full-branch review findings for vector state animation
    
    Consolidates three critical blockers and six should-fix items from the
    full-branch code review of the 41-commit vector state animation feature
    (Phases 1–8.4). All changes are single-commit to support atomicity.
    
    ## Blocker 1: Vector part description memory leak on collection unload
    
    The EDJE_PART_TYPE_VECTOR case was missing from both cleanup hooks in
    edje_load.c, leaving vg.overrides lists and their nested allocations
    (stroke_dash arrays, gradient stops) on the heap for every part
    description in every loaded theme. The leak persisted across .edj file
    loads and reloads because Edje collections are cached in-memory.
    
    Added _edje_collection_free_part_description_clean and its sibling
    _edje_collection_free_part_description_free handlers, mirroring the
    external-params cleanup pattern: when free_strings is true (stringshare
    mode), use _edje_vg_override_free; when false (Eet dict pointers), free
    sub-arrays directly and the struct (Eet dict strings must not be freed).
    This is the production memory leak referenced in the PR body.
    
    ## Blocker 2: Compiler silently writes corrupted .edj on tree-id patch failure
    
    In edje_cc_out.c, the vg desc->tree_id patch loop was issuing a warning
    and continuing when a tree was not found in the index map. This was
    silently leaving tree_id=-1 on disk, producing a theme that renders no
    vector output. Reproduction: any vector.use: reference pointing to a
    non-existent or incorrectly-indexed tree would produce a corrupted .edj.
    
    Promoted the warning to error_and_abort with the actual tree name (not
    %p) and a message explaining the internal consistency failure. Actionable
    for theme developers: they can now see which tree failed to index.
    
    ## Blocker 3: edje_edit tree_id fixup loop does not page in unloaded collections
    
    When edje_edit_vg_tree_del removes a tree from vd->trees, the second-pass
    loop that decrements tree_id in all remaining parts was only visiting
    collections already loaded (ce2->ref). Groups not currently in memory
    (e.g., unloaded via Edje_Cache) retained stale tree_id references past
    the removed slot, silently pointing to wrong trees.
    
    Added _edje_file_coll_open + _edje_cache_coll_unref pattern in the
    second-pass loop to match the first-pass (in-use group) scan. This
    ensures all collection groups in the file, loaded or not, have their
    tree_id adjusted consistently. Comment clarifies the "mirror the
    first-pass pattern" intent for future maintainers.
    
    ## Should-fix 3: Transition-end stale cache slot leak
    
    When a transition completes (ep->param2 becomes NULL) but the previous
    transition left an Efl_VG object in rpv->cache_param2, the object was
    never released. This created a per-completed-transition, per-part leak
    lasting for the object lifetime.
    
    Added a slot-clear call when !ep->param2 && rpv->cache_param2.efl_vg
    to release stale B-side cache entries. Scales from one leak per transition
    to no leaks.
    
    ## Should-fix 4: Color class resolution did not properly clear the sentinel
    
    _resolve_binding was using eina_stringshare_replace(..., "") to mark
    resolved bindings, leaving an empty stringshare sentinel. This was
    fragile: future passes that call for_each_class_name would skip the
    binding (checking *cb->color_class), but direct dereference code (like
    new diagnostic or iteration utilities) could break if they expected
    NULL to mean "no class."
    
    Changed to eina_stringshare_del(cb->color_class) + NULL assignment.
    NULL is now the definitive "resolved or not found" sentinel. Updated
    the doc comment in edje_vg_tree.h to reflect this change.
    
    ## Should-fix 5: Cascading VEC014 diagnostics on same node
    
    When VEC014 (transform-form conflict) was demoted from hard error to
    warning, the conflict path did not return early. Subsequent statements
    on the same node would re-evaluate and re-emit the diagnostic, resulting
    in spammy output and potentially incorrect error counts in logs.
    
    Added return; after edje_diag_emit in the conflict path so the bitset
    tracks the error and later statements don't re-emit.
    
    ## Should-fix 6: Early-abort trees leak their root subtrees
    
    When _edje_cc_vg_cleanup runs, it expects all trees to have had
    shallow-copy (root nullified). On early abort paths (e.g., VEC001
    duplicate-name error), the shallow-copy never ran and root was live.
    This left unfreed root Edle_Vg_Node subtrees.
    
    Added _edje_vg_tree_free_fields(t, EINA_TRUE) before free(t) to
    unconditionally free roots. Comment documents the two paths (normal
    shallow-copy vs. early abort).
    
    ## Should-fix 7: Example build list alphabetical ordering
    
    The edje-vector-states example was listed before edje-swallow, breaking
    alphabetical order in meson.build. Moved it after edje-swallow2 to
    restore ordering and make future additions easier to spot.
    
    Deliberately NOT addressed:
    - S1 (forward-decl support for vector.use:) requires Eet on-disk format
      change; defer to follow-up.
    - S2 (free() call placement) was false positive in the original review;
      code was already correct.
    - S8 (uniform EAPI decoration) follows project convention; no change.
    
    All 91 edje_suite tests pass. Reviewer-of-fix nits (B2 message polish,
    stale doc comment) also addressed in this commit.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
 src/bin/edje/edje_cc_handlers.c |  9 ++++++++-
 src/bin/edje/edje_cc_out.c      |  7 +++++--
 src/examples/edje/meson.build   |  2 +-
 src/lib/edje/edje_calc.c        |  5 +++++
 src/lib/edje/edje_edit.c        | 27 ++++++++++++++++++++++++---
 src/lib/edje/edje_load.c        | 33 +++++++++++++++++++++++++++++++++
 src/lib/edje/edje_vg_tree.c     |  7 ++++---
 src/lib/edje/edje_vg_tree.h     | 11 +++++------
 8 files changed, 85 insertions(+), 16 deletions(-)

diff --git a/src/bin/edje/edje_cc_handlers.c b/src/bin/edje/edje_cc_handlers.c
index 91379ed2e8..f75d1d6aa2 100644
--- a/src/bin/edje/edje_cc_handlers.c
+++ b/src/bin/edje/edje_cc_handlers.c
@@ -229,6 +229,9 @@ _vg_xform_track(int new_bit)
                        "transform.matrix and decomposed transform "
                        "(translate/rotate/scale) are mutually exclusive on "
                        "the same node within one description");
+        /* Do not update the bitset after a conflict: subsequent statements
+           on the same node must not trigger additional cascading diagnostics. */
+        return;
      }
 
    uintptr_t updated = prev | (uintptr_t)new_bit;
@@ -270,7 +273,11 @@ _edje_cc_vg_cleanup(void)
         Edje_Vg_Tree *t;
         EINA_LIST_FOREACH(all_vg_trees, l, t)
           {
-             /* root was nullified by shallow-copy; free only the wrapper. */
+             /* root was nullified by shallow-copy in the normal path.
+                On early abort (e.g. VEC001 duplicate-name error), the
+                shallow-copy never ran and root is still live — so always
+                call _edje_vg_tree_free_fields before freeing the wrapper. */
+             _edje_vg_tree_free_fields(t, EINA_TRUE);
              free(t);
           }
         eina_list_free(all_vg_trees);
diff --git a/src/bin/edje/edje_cc_out.c b/src/bin/edje/edje_cc_out.c
index 37c6ac204a..31134180aa 100644
--- a/src/bin/edje/edje_cc_out.c
+++ b/src/bin/edje/edje_cc_out.c
@@ -1623,8 +1623,11 @@ data_write_vectors_trees(Eet_File *ef, int *tree_num)
                 uintptr_t idx_plus_1 = (uintptr_t)eina_hash_find(idx_map, &t);
                 if (idx_plus_1 == 0)
                   {
-                     WRN("vg desc->tree_id patch: tree not found in index map");
-                     continue;
+                     error_and_abort(ef,
+                        "vg desc->tree_id patch: tree '%s' (tree_id=%d) not found "
+                        "in index map — internal consistency error; this theme may "
+                        "have been produced by an incompatible tool",
+                        t->name ? t->name : "<unnamed>", desc->vg.tree_id);
                   }
                 desc->vg.tree_id = (int)(idx_plus_1 - 1);
              }
diff --git a/src/examples/edje/meson.build b/src/examples/edje/meson.build
index 58e8f8fa87..07340ca50a 100644
--- a/src/examples/edje/meson.build
+++ b/src/examples/edje/meson.build
@@ -118,9 +118,9 @@ edje_examples = [
   'edje-multisense',
   'edje-perspective',
   'edje-signals-messages',
-  'edje-vector-states',
   'edje-swallow',
   'edje-swallow2',
+  'edje-vector-states',
   'edje-table',
   'edje-text',
   'edje-textblock-hyphenation',
diff --git a/src/lib/edje/edje_calc.c b/src/lib/edje/edje_calc.c
index 5630a5775e..2b78067557 100644
--- a/src/lib/edje/edje_calc.c
+++ b/src/lib/edje/edje_calc.c
@@ -3322,6 +3322,11 @@ _edje_vector_recalc_apply(Edje *ed, Edje_Real_Part *ep, Edje_Calc_Params *p3 EIN
              return;
           }
 
+        /* No transition: release the B-side cache slot if it holds a stale
+           entry from a previous transition on the same part. */
+        if (!ep->param2 && rpv->cache_param2.efl_vg)
+          _edje_vg_cache_slot_clear(&rpv->cache_param2);
+
         /* Transition active? (param2 set and pos meaningfully non-zero) */
         if (ep->param2 && NEQ(pos, ZERO))
           {
diff --git a/src/lib/edje/edje_edit.c b/src/lib/edje/edje_edit.c
index 48f0ccb846..49ff67b1b2 100644
--- a/src/lib/edje/edje_edit.c
+++ b/src/lib/edje/edje_edit.c
@@ -17424,7 +17424,10 @@ edje_edit_vg_tree_del(Evas_Object *obj, const char *name)
         memmove(&vd->trees[tree_idx], &vd->trees[tree_idx + 1],
                 sizeof(Edje_Vg_Tree) * (vd->trees_count - (unsigned int)tree_idx - 1));
 
-        /* Fix up tree_id references that pointed past the removed slot. */
+        /* Fix up tree_id references that pointed past the removed slot.
+         * Mirror the first-pass pattern exactly: page in unloaded collections
+         * via _edje_file_coll_open so groups not currently in use still get
+         * their tree_id adjusted. */
         if (ed->file->collection)
           {
              Eina_Iterator *it2 =
@@ -17433,8 +17436,24 @@ edje_edit_vg_tree_del(Evas_Object *obj, const char *name)
 
              EINA_ITERATOR_FOREACH(it2, ce2)
                {
-                  Edje_Part_Collection *col = ce2->ref;
-                  if (!col) continue;
+                  Edje_Part_Collection *col;
+                  Eina_Bool transient2 = EINA_FALSE;
+
+                  if (ce2->ref)
+                    {
+                       col = ce2->ref;
+                    }
+                  else
+                    {
+                       col = _edje_file_coll_open(ed->file, ce2->entry);
+                       transient2 = EINA_TRUE;
+                    }
+
+                  if (!col)
+                    {
+                       continue;
+                    }
+
                   for (pi = 0; pi < col->parts_count; ++pi)
                     {
                        Edje_Part *ep = col->parts[pi];
@@ -17452,6 +17471,8 @@ edje_edit_vg_tree_del(Evas_Object *obj, const char *name)
                               vdesc->vg.tree_id--;
                          }
                     }
+
+                  if (transient2) _edje_cache_coll_unref(ed->file, col);
                }
              eina_iterator_free(it2);
           }
diff --git a/src/lib/edje/edje_load.c b/src/lib/edje/edje_load.c
index 043413b562..849c9c1b4d 100644
--- a/src/lib/edje/edje_load.c
+++ b/src/lib/edje/edje_load.c
@@ -2606,6 +2606,38 @@ _edje_collection_free_part_description_clean(int type, Edje_Part_Description_Com
              eina_stringshare_del(text->text.font.str);
           }
         break;
+
+      case EDJE_PART_TYPE_VECTOR:
+        {
+           Edje_Part_Description_Vector *svg;
+           Edje_Vg_Override *ovr;
+
+           svg = (Edje_Part_Description_Vector *)desc;
+           if (free_strings)
+             {
+                /* Strings in overrides are stringshares — use full free. */
+                EINA_LIST_FREE(svg->vg.overrides, ovr)
+                  _edje_vg_override_free(ovr);
+             }
+           else
+             {
+                /* Strings are Eet dictionary pointers (mmap-backed); do NOT
+                   free them.  Sub-arrays (stroke_dash, gradient stops) are
+                   still heap-allocated by Eet even in mmap mode and must be
+                   freed.  Mirror the pattern of _edje_external_params_free:
+                   free arrays and the struct, skip string fields. */
+                EINA_LIST_FREE(svg->vg.overrides, ovr)
+                  {
+                     if (ovr->expected_type == EDJE_VG_NODE_SHAPE)
+                       free(ovr->payload.shape.stroke_dash);
+                     else if (ovr->expected_type == EDJE_VG_NODE_GRADIENT_LINEAR ||
+                              ovr->expected_type == EDJE_VG_NODE_GRADIENT_RADIAL)
+                       free(ovr->payload.gradient.stops);
+                     free(ovr);
+                  }
+             }
+        }
+        break;
      }
 }
 
@@ -2636,6 +2668,7 @@ case EDJE_PART_TYPE_##Type: eina_mempool_free(ce->mp->mp.Type, Desc); \
         FREE_POOL(EXTERNAL, ce, desc);
         FREE_POOL(SNAPSHOT, ce, desc);
         FREE_POOL(SPACER, ce, desc);
+        FREE_POOL(VECTOR, ce, desc);
      }
 }
 
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index 15ed5e9d0c..dd1b8bf5d1 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -1852,7 +1852,7 @@ _edje_vg_cache_get_or_build(Edje_Vg_Cache_Slot *slot,
 static void
 _resolve_binding(Edje_Vg_Color_Binding *cb, const Edje *ed)
 {
-   if (!cb || !cb->color_class || !*cb->color_class) return;
+   if (!cb || !cb->color_class) return;
 
    Edje_Color_Class *cc = _edje_color_class_recursive_find(ed, cb->color_class);
    if (cc)
@@ -1863,7 +1863,8 @@ _resolve_binding(Edje_Vg_Color_Binding *cb, const Edje *ed)
         cb->a = (cb->a * cc->a) / 255;
      }
    /* Hit or miss: clear so subsequent passes see resolved RGBA. */
-   eina_stringshare_replace(&cb->color_class, "");
+   eina_stringshare_del(cb->color_class);
+   cb->color_class = NULL;
 }
 
 static void
@@ -1911,7 +1912,7 @@ static void
 _emit_binding_class(const Edje_Vg_Color_Binding *b,
                     Edje_Vg_Class_Cb cb, void *data)
 {
-   if (b->color_class && *b->color_class)
+   if (b->color_class)
      cb(b->color_class, data);
 }
 
diff --git a/src/lib/edje/edje_vg_tree.h b/src/lib/edje/edje_vg_tree.h
index 7e532284e6..4bc5bdaf1b 100644
--- a/src/lib/edje/edje_vg_tree.h
+++ b/src/lib/edje/edje_vg_tree.h
@@ -188,15 +188,14 @@ EAPI void _edje_vg_cache_slot_clear(Edje_Vg_Cache_Slot *slot);
 /* --- Phase 6.2: class color resolution ----------------------------------- */
 
 /* Walk the working tree; for every Edje_Vg_Color_Binding whose
- * color_class is non-NULL/non-empty, look up the class via
+ * color_class is non-NULL, look up the class via
  * _edje_color_class_recursive_find(ed, name). On hit, multiply the
  * binding's raw RGBA componentwise (resolved.r = base.r * C.r / 255).
- * Then clear color_class to "" (empty stringshare) so subsequent passes
- * (lerp / materialize) see only resolved RGBA.
+ * Then release color_class (eina_stringshare_del + NULL) so subsequent
+ * passes (lerp / materialize) see only resolved RGBA.
  *
- * Empty string is the "no class" sentinel — left untouched. Class
- * not found: clear the binding's class so subsequent recalcs don't keep
- * looking it up.  No-op when working or ed is NULL. */
+ * Class not found: also release the binding's class so subsequent
+ * recalcs don't keep looking it up.  No-op when working or ed is NULL. */
 EAPI void _edje_vg_tree_resolve_colors(Edje_Vg_Tree *working, const Edje *ed);
 
 /* --- Phase 6.1: class-name walk helpers ---------------------------------- */

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to