Commit b75e61bf for libheif

commit b75e61bfb4863bd78839e33268b10d4a1e85faf0
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Fri May 15 12:33:37 2026 +0200

    resolve endless recursion in get_id_of_non_virtual_child_image()

diff --git a/libheif/context.cc b/libheif/context.cc
index 9b24343d..2cadeed8 100644
--- a/libheif/context.cc
+++ b/libheif/context.cc
@@ -31,6 +31,7 @@
 #include <limits>
 #include <cmath>
 #include <deque>
+#include <set>
 #include "image-items/image_item.h"
 #include <codecs/hevc_boxes.h>
 #include "sequences/track.h"
@@ -1347,47 +1348,54 @@ bool HeifContext::has_alpha(heif_item_id ID) const
 }


-Error HeifContext::get_id_of_non_virtual_child_image(heif_item_id id, heif_item_id& out) const
+Result<heif_item_id> HeifContext::find_first_coded_image_id(heif_item_id id) const
 {
-  uint32_t image_type = m_heif_file->get_item_type_4cc(id);
-  if (image_type == fourcc("grid") ||
-      image_type == fourcc("iden") ||
-      image_type == fourcc("iovl")) {
-    auto iref_box = m_heif_file->get_iref_box();
-    if (!iref_box) {
+  std::set<heif_item_id> visited;
+
+  for (;;) {
+    if (!visited.insert(id).second) {
       return Error(heif_error_Invalid_input,
                    heif_suberror_No_item_data,
-                   "Derived image does not reference any other image items");
+                   "Derived image references form a cycle");
     }

-    std::vector<heif_item_id> image_references = iref_box->get_references(id, fourcc("dimg"));
+    uint32_t image_type = m_heif_file->get_item_type_4cc(id);
+    if (image_type == fourcc("grid") ||
+        image_type == fourcc("iden") ||
+        image_type == fourcc("iovl")) {
+      auto iref_box = m_heif_file->get_iref_box();
+      if (!iref_box) {
+        return Error(heif_error_Invalid_input,
+                     heif_suberror_No_item_data,
+                     "Derived image does not reference any other image items");
+      }
+
+      std::vector<heif_item_id> image_references = iref_box->get_references(id, fourcc("dimg"));

-    // TODO: check whether this really can be recursive (e.g. overlay of grid images)
+      if (image_references.empty()) {
+        return Error(heif_error_Invalid_input,
+                     heif_suberror_No_item_data,
+                     "Derived image does not reference any other image items");
+      }

-    if (image_references.empty() || image_references[0] == id) {
-      return Error(heif_error_Invalid_input,
-                   heif_suberror_No_item_data,
-                   "Derived image does not reference any other image items");
+      // follow the first reference
+      id = image_references[0];
     }
     else {
-      return get_id_of_non_virtual_child_image(image_references[0], out);
-    }
-  }
-  else {
-    if (!m_all_images.contains(id)) {
-      std::stringstream sstr;
-      sstr << "Image item " << id << " referenced, but it does not exist\n";
+      if (!m_all_images.contains(id)) {
+        std::stringstream sstr;
+        sstr << "Image item " << id << " referenced, but it does not exist\n";

-      return Error(heif_error_Invalid_input,
-        heif_suberror_Nonexisting_item_referenced,
-        sstr.str());
-    }
-    else if (dynamic_cast<ImageItem_Error*>(m_all_images.find(id)->second.get())) {
-      // Should er return an error here or leave it to the follow-up code to detect that?
-    }
+        return Error(heif_error_Invalid_input,
+          heif_suberror_Nonexisting_item_referenced,
+          sstr.str());
+      }
+      else if (dynamic_cast<ImageItem_Error*>(m_all_images.find(id)->second.get())) {
+        // Should we return an error here or leave it to the follow-up code to detect that?
+      }

-    out = id;
-    return Error::Ok;
+      return id;
+    }
   }
 }

diff --git a/libheif/context.h b/libheif/context.h
index e773ce0b..898111fd 100644
--- a/libheif/context.h
+++ b/libheif/context.h
@@ -131,7 +131,7 @@ public:
                                                                        heif_chroma out_chroma,
                                                                        const heif_decoding_options& options) const;

-  Error get_id_of_non_virtual_child_image(heif_item_id in, heif_item_id& out) const;
+  Result<heif_item_id> find_first_coded_image_id(heif_item_id in) const;

   std::string debug_dump_boxes() const;

diff --git a/libheif/image-items/grid.cc b/libheif/image-items/grid.cc
index 55658808..08f07c4c 100644
--- a/libheif/image-items/grid.cc
+++ b/libheif/image-items/grid.cc
@@ -656,13 +656,12 @@ void ImageItem_Grid::get_tile_size(uint32_t& w, uint32_t& h) const

 int ImageItem_Grid::get_luma_bits_per_pixel() const
 {
-  heif_item_id child;
-  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
-  if (err) {
+  auto child_result = get_context()->find_first_coded_image_id(get_id());
+  if (child_result.is_error()) {
     return -1;
   }

-  auto image = get_context()->get_image(child, true);
+  auto image = get_context()->get_image(*child_result, true);
   if (!image) {
     return -1;
   }
@@ -673,25 +672,23 @@ int ImageItem_Grid::get_luma_bits_per_pixel() const

 int ImageItem_Grid::get_chroma_bits_per_pixel() const
 {
-  heif_item_id child;
-  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
-  if (err) {
+  auto child_result = get_context()->find_first_coded_image_id(get_id());
+  if (child_result.is_error()) {
     return -1;
   }

-  auto image = get_context()->get_image(child, true);
+  auto image = get_context()->get_image(*child_result, true);
   return image->get_chroma_bits_per_pixel();
 }

 Result<std::shared_ptr<Decoder>> ImageItem_Grid::get_decoder() const
 {
-  heif_item_id child;
-  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
-  if (err) {
-    return {err};
+  auto child_result = get_context()->find_first_coded_image_id(get_id());
+  if (child_result.is_error()) {
+    return child_result.error();
   }

-  auto image = get_context()->get_image(child, true);
+  auto image = get_context()->get_image(*child_result, true);
   if (!image) {
     return Error{heif_error_Invalid_input,
       heif_suberror_Nonexisting_item_referenced};
diff --git a/libheif/image-items/iden.cc b/libheif/image-items/iden.cc
index b62a3b1b..8050af27 100644
--- a/libheif/image-items/iden.cc
+++ b/libheif/image-items/iden.cc
@@ -99,13 +99,12 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem_iden::decode_compressed_image(

 Error ImageItem_iden::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const
 {
-  heif_item_id child;
-  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
-  if (err) {
-    return err;
+  auto child_result = get_context()->find_first_coded_image_id(get_id());
+  if (child_result.is_error()) {
+    return child_result.error();
   }

-  auto image = get_context()->get_image(child, true);
+  auto image = get_context()->get_image(*child_result, true);
   if (!image) {
     return Error{heif_error_Invalid_input,
                  heif_suberror_Nonexisting_item_referenced};
@@ -117,13 +116,12 @@ Error ImageItem_iden::get_coded_image_colorspace(heif_colorspace* out_colorspace

 int ImageItem_iden::get_luma_bits_per_pixel() const
 {
-  heif_item_id child;
-  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
-  if (err) {
+  auto child_result = get_context()->find_first_coded_image_id(get_id());
+  if (child_result.is_error()) {
     return -1;
   }

-  auto image = get_context()->get_image(child, true);
+  auto image = get_context()->get_image(*child_result, true);
   if (!image) {
     return -1;
   }
@@ -134,13 +132,12 @@ int ImageItem_iden::get_luma_bits_per_pixel() const

 int ImageItem_iden::get_chroma_bits_per_pixel() const
 {
-  heif_item_id child;
-  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
-  if (err) {
+  auto child_result = get_context()->find_first_coded_image_id(get_id());
+  if (child_result.is_error()) {
     return -1;
   }

-  auto image = get_context()->get_image(child, true);
+  auto image = get_context()->get_image(*child_result, true);
   if (!image) {
     return -1;
   }
@@ -161,13 +158,12 @@ void ImageItem_iden::populate_component_descriptions()
     return;
   }

-  heif_item_id child_id;
-  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child_id);
-  if (err) {
+  auto child_result = get_context()->find_first_coded_image_id(get_id());
+  if (child_result.is_error()) {
     return;
   }

-  auto child = get_context()->get_image(child_id, true);
+  auto child = get_context()->get_image(*child_result, true);
   if (!child) {
     return;
   }
diff --git a/libheif/image-items/overlay.cc b/libheif/image-items/overlay.cc
index 705a9338..3e01075c 100644
--- a/libheif/image-items/overlay.cc
+++ b/libheif/image-items/overlay.cc
@@ -396,26 +396,24 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem_Overlay::decode_overlay_image(

 int ImageItem_Overlay::get_luma_bits_per_pixel() const
 {
-  heif_item_id child;
-  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
-  if (err) {
+  auto child_result = get_context()->find_first_coded_image_id(get_id());
+  if (child_result.is_error()) {
     return -1;
   }

-  auto image = get_context()->get_image(child, true);
+  auto image = get_context()->get_image(*child_result, true);
   return image->get_luma_bits_per_pixel();
 }


 int ImageItem_Overlay::get_chroma_bits_per_pixel() const
 {
-  heif_item_id child;
-  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
-  if (err) {
+  auto child_result = get_context()->find_first_coded_image_id(get_id());
+  if (child_result.is_error()) {
     return -1;
   }

-  auto image = get_context()->get_image(child, true);
+  auto image = get_context()->get_image(*child_result, true);
   return image->get_chroma_bits_per_pixel();
 }