Detect iref repetition before ItemReadAndParse() (#2315)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ade277..70b8ab3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,8 @@
   https://github.com/AOMediaCodec/libavif/issues/2274.
 * Fix out-of-order 'dimg' grid associations
   https://github.com/AOMediaCodec/libavif/issues/2311.
+* Report files with an item used in multiple 'dimg' boxes with
+  AVIF_RESULT_NOT_IMPLEMENTED instead of AVIF_RESULT_INVALID_IMAGE_GRID.
 
 ## [1.1.0] - 2024-07-11
 
diff --git a/src/read.c b/src/read.c
index 46f6b42..cbc8c0b 100644
--- a/src/read.c
+++ b/src/read.c
@@ -198,9 +198,7 @@
     uint32_t auxForID;             // if non-zero, this item is an auxC plane for Item #{auxForID}
     uint32_t descForID;            // if non-zero, this item is a content description for Item #{descForID}
     uint32_t dimgForID;            // if non-zero, this item is an input of derived Item #{dimgForID}
-    // If dimgForId is non-zero, this is the zero-based index of this item in the list of Item #{dimgForID}'s dimg
-    // Note that if the same item appears multiple times in the dimg box, this is the last index for this item.
-    uint32_t dimgIdx;
+    uint32_t dimgIdx; // If dimgForId is non-zero, this is the zero-based index of this item in the list of Item #{dimgForID}'s dimg.
     avifBool hasDimgFrom; // whether there is a 'dimg' box with this item's id as 'fromID'
     uint32_t premByID;    // if non-zero, this item is premultiplied by Item #{premByID}
     avifBool hasUnsupportedEssentialProperty; // If true, this item cites a property flagged as 'essential' that libavif doesn't support (yet). Ignore the item, if so.
@@ -2154,9 +2152,7 @@
                     ++dimgItemCount;
                 }
             }
-            if (dimgItemCount != grid->rows * grid->columns) {
-                return AVIF_RESULT_INVALID_IMAGE_GRID;
-            }
+            AVIF_CHECKERR(dimgItemCount == grid->rows * grid->columns, AVIF_RESULT_INVALID_IMAGE_GRID);
         } else {
             // item was generated for convenience and is not part of the bitstream.
             // grid information should already be set.
@@ -2914,6 +2910,12 @@
                 avifDecoderItem * dimg;
                 AVIF_CHECKRES(avifMetaFindOrCreateItem(meta, toID, &dimg));
 
+                // Section 8.11.12.1 of ISO/IEC 14496-12:
+                //   The items linked to are then represented by an array of to_item_IDs;
+                //   within a given array, a given value shall occur at most once.
+                AVIF_CHECKERR(dimg->dimgForID != fromID, AVIF_RESULT_INVALID_IMAGE_GRID);
+                // A given value may occur within multiple arrays but this is not supported by libavif.
+                AVIF_CHECKERR(dimg->dimgForID == 0, AVIF_RESULT_NOT_IMPLEMENTED);
                 dimg->dimgForID = fromID;
                 dimg->dimgIdx = refIndex;
             } else if (!memcmp(irefHeader.type, "prem", 4)) {
diff --git a/tests/data/README.md b/tests/data/README.md
index 2996eb9..2481a59 100644
--- a/tests/data/README.md
+++ b/tests/data/README.md
@@ -363,6 +363,15 @@
 Source: Personal photo converted with `avifenc --grid 1x5 --yuv 420` at
 commit [632d131](https://github.com/AOMediaCodec/libavif/commit/632d13188f9b7faa40f20d870e792174b8b5b8e6).
 
+### File [sofa_grid1x5_420_dimg_repeat.avif](sofa_grid1x5_420_dimg_repeat.avif)
+
+![](sofa_grid1x5_420_dimg_repeat.avif)
+
+License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE)
+
+Source: `sofa_grid1x5_420.avif` with manually edited `dimg` associations
+2,3,4,5,5 instead of 2,3,4,5,6 (invalid file).
+
 ### File [sofa_grid1x5_420_reversed_dimg_order.avif](sofa_grid1x5_420_reversed_dimg_order.avif)
 
 ![](sofa_grid1x5_420_reversed_dimg_order.avif)
@@ -399,6 +408,18 @@
 [alpha] [alpha]
 ```
 
+### File [color_grid_alpha_grid_tile_shared_in_dimg.avif](color_grid_alpha_grid_tile_shared_in_dimg.avif)
+
+![](color_grid_alpha_grid_tile_shared_in_dimg.avif)
+
+License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE)
+
+Source: Same as `color_grid_alpha_nogrid.avif`
+
+The color and alpha planes are arranged as 2x2 grid items. The color `dimg`
+associations are manually edited from 2,3,4,5 to 2,3,4,A. The item with ID 0xA
+is an item used in two grids.
+
 ### File [alpha_noispe.avif](alpha_noispe.avif)
 
 License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE)
diff --git a/tests/data/color_grid_alpha_grid_tile_shared_in_dimg.avif b/tests/data/color_grid_alpha_grid_tile_shared_in_dimg.avif
new file mode 100644
index 0000000..64b47dd
--- /dev/null
+++ b/tests/data/color_grid_alpha_grid_tile_shared_in_dimg.avif
Binary files differ
diff --git a/tests/data/sofa_grid1x5_420_dimg_repeat.avif b/tests/data/sofa_grid1x5_420_dimg_repeat.avif
new file mode 100644
index 0000000..4d48528
--- /dev/null
+++ b/tests/data/sofa_grid1x5_420_dimg_repeat.avif
Binary files differ
diff --git a/tests/gtest/avifdimgtest.cc b/tests/gtest/avifdimgtest.cc
index c166c5b..e3ce635 100644
--- a/tests/gtest/avifdimgtest.cc
+++ b/tests/gtest/avifdimgtest.cc
@@ -18,7 +18,36 @@
 
 //------------------------------------------------------------------------------
 
-TEST(IncrementalTest, Dimg) {
+TEST(DimgTest, IrefRepetition) {
+  testutil::AvifRwData avif = testutil::ReadFile(
+      std::string(data_path) + "sofa_grid1x5_420_dimg_repeat.avif");
+  ASSERT_NE(avif.size, 0u);
+  ImagePtr reference(avifImageCreateEmpty());
+  ASSERT_NE(reference, nullptr);
+  DecoderPtr decoder(avifDecoderCreate());
+  ASSERT_NE(decoder, nullptr);
+  ASSERT_EQ(avifDecoderReadMemory(decoder.get(), reference.get(), avif.data,
+                                  avif.size),
+            AVIF_RESULT_INVALID_IMAGE_GRID);
+}
+
+TEST(DimgTest, ItemShared) {
+  testutil::AvifRwData avif =
+      testutil::ReadFile(std::string(data_path) +
+                         "color_grid_alpha_grid_tile_shared_in_dimg.avif");
+  ASSERT_NE(avif.size, 0u);
+  ImagePtr reference(avifImageCreateEmpty());
+  ASSERT_NE(reference, nullptr);
+  DecoderPtr decoder(avifDecoderCreate());
+  ASSERT_NE(decoder, nullptr);
+  ASSERT_EQ(avifDecoderReadMemory(decoder.get(), reference.get(), avif.data,
+                                  avif.size),
+            AVIF_RESULT_NOT_IMPLEMENTED);
+}
+
+//------------------------------------------------------------------------------
+
+TEST(DimgTest, ItemOutOfOrder) {
   testutil::AvifRwData avif =
       testutil::ReadFile(std::string(data_path) + "sofa_grid1x5_420.avif");
   ASSERT_NE(avif.size, 0u);