@@ -10416,3 +10416,153 @@ OCIO_ADD_TEST(Config, interchange_attributes)
1041610416 }
1041710417}
1041810418
10419+ OCIO_ADD_TEST (Config, cyclic_color_space_transform)
10420+ {
10421+ // Regression test for a stack overflow triggered by a self-referential
10422+ // ColorSpaceTransform (a ColorSpace whose from_reference is a ColorSpaceTransform
10423+ // that points back to itself). getProcessor() must throw a clean exception
10424+ // rather than recursing until the stack guard page is hit.
10425+
10426+ constexpr char CYCLIC_PROFILE [] =
10427+ " ocio_profile_version: 2\n "
10428+ " roles:\n "
10429+ " default: cs0\n "
10430+ " colorspaces:\n "
10431+ " - !<ColorSpace>\n "
10432+ " name: cs0\n "
10433+ " isdata: true\n "
10434+ " - !<ColorSpace>\n "
10435+ " name: cs1\n "
10436+ " from_scene_reference: !<ColorSpaceTransform> {src: cs0, dst: cs1}\n " ;
10437+
10438+ std::istringstream is;
10439+ is.str (CYCLIC_PROFILE );
10440+ OCIO ::ConstConfigRcPtr config;
10441+ OCIO_CHECK_NO_THROW (config = OCIO::Config::CreateFromStream (is));
10442+
10443+ OCIO_CHECK_THROW_WHAT (config->getProcessor (" cs0" , " cs1" ),
10444+ OCIO ::Exception,
10445+ " Cycle detected" );
10446+ }
10447+
10448+ OCIO_ADD_TEST (Config, cyclic_color_space_two_step)
10449+ {
10450+ // Two-step cycle: cs1 -> cs2 -> cs1. Verify the depth guard catches indirect cycles too.
10451+
10452+ constexpr char CYCLIC_PROFILE [] =
10453+ " ocio_profile_version: 2\n "
10454+ " roles:\n "
10455+ " default: cs0\n "
10456+ " colorspaces:\n "
10457+ " - !<ColorSpace>\n "
10458+ " name: cs0\n "
10459+ " isdata: true\n "
10460+ " - !<ColorSpace>\n "
10461+ " name: cs1\n "
10462+ " from_scene_reference: !<ColorSpaceTransform> {src: cs0, dst: cs2}\n "
10463+ " - !<ColorSpace>\n "
10464+ " name: cs2\n "
10465+ " from_scene_reference: !<ColorSpaceTransform> {src: cs0, dst: cs1}\n " ;
10466+
10467+ std::istringstream is;
10468+ is.str (CYCLIC_PROFILE );
10469+ OCIO ::ConstConfigRcPtr config;
10470+ OCIO_CHECK_NO_THROW (config = OCIO::Config::CreateFromStream (is));
10471+
10472+ OCIO_CHECK_THROW_WHAT (config->getProcessor (" cs0" , " cs1" ),
10473+ OCIO ::Exception,
10474+ " Cycle detected" );
10475+ }
10476+
10477+ OCIO_ADD_TEST (Config, cyclic_look_transform)
10478+ {
10479+ // Regression test: a Look whose transform is a LookTransform that references the
10480+ // same look must not crash via stack overflow on getProcessor(). Triggered here
10481+ // by a ColorSpace whose from_scene_reference is a LookTransform invoking lookA,
10482+ // whose own transform is another LookTransform invoking lookA.
10483+
10484+ constexpr char CYCLIC_PROFILE [] =
10485+ " ocio_profile_version: 2\n "
10486+ " roles:\n "
10487+ " default: cs0\n "
10488+ " looks:\n "
10489+ " - !<Look>\n "
10490+ " name: lookA\n "
10491+ " process_space: cs0\n "
10492+ " transform: !<LookTransform> {src: cs0, dst: cs0, looks: lookA}\n "
10493+ " colorspaces:\n "
10494+ " - !<ColorSpace>\n "
10495+ " name: cs0\n "
10496+ " - !<ColorSpace>\n "
10497+ " name: cs1\n "
10498+ " from_scene_reference: !<LookTransform> {src: cs0, dst: cs0, looks: lookA}\n " ;
10499+
10500+ std::istringstream is;
10501+ is.str (CYCLIC_PROFILE );
10502+ OCIO ::ConstConfigRcPtr config;
10503+ OCIO_CHECK_NO_THROW (config = OCIO::Config::CreateFromStream (is));
10504+
10505+ OCIO_CHECK_THROW_WHAT (config->getProcessor (" cs0" , " cs1" ),
10506+ OCIO ::Exception,
10507+ " Cycle detected" );
10508+ }
10509+
10510+ OCIO_ADD_TEST (Config, cyclic_color_space_linearity_check)
10511+ {
10512+ // Regression test for the BuildOps depth guard. Config::isColorSpaceLinear() reaches
10513+ // the op-builder via Config::Impl::getProcessorWithoutCaching(), which does NOT call
10514+ // CollectContextVariables() first. So even with the CollectContextVariables guard,
10515+ // this path will still infinite-recurse on a cyclic color space unless BuildOps
10516+ // independently guards against it.
10517+
10518+ constexpr char CYCLIC_PROFILE [] =
10519+ " ocio_profile_version: 2\n "
10520+ " roles:\n "
10521+ " default: cs0\n "
10522+ " colorspaces:\n "
10523+ " - !<ColorSpace>\n "
10524+ " name: cs0\n "
10525+ " - !<ColorSpace>\n "
10526+ " name: cs1\n "
10527+ " from_scene_reference: !<ColorSpaceTransform> {src: cs0, dst: cs1}\n " ;
10528+
10529+ std::istringstream is;
10530+ is.str (CYCLIC_PROFILE );
10531+ OCIO ::ConstConfigRcPtr config;
10532+ OCIO_CHECK_NO_THROW (config = OCIO::Config::CreateFromStream (is));
10533+
10534+ OCIO_CHECK_THROW_WHAT (config->isColorSpaceLinear (" cs1" , OCIO ::REFERENCE_SPACE_SCENE ),
10535+ OCIO ::Exception,
10536+ " Cycle detected" );
10537+ }
10538+
10539+ OCIO_ADD_TEST (Config, cyclic_display_view_transform)
10540+ {
10541+ // Regression test: a ColorSpace whose from_scene_reference is a DisplayViewTransform
10542+ // that ultimately points back to the same ColorSpace (via the view's color space)
10543+ // must not crash via stack overflow on getProcessor().
10544+
10545+ constexpr char CYCLIC_PROFILE [] =
10546+ " ocio_profile_version: 2\n "
10547+ " roles:\n "
10548+ " default: cs0\n "
10549+ " displays:\n "
10550+ " D1:\n "
10551+ " - !<View> {name: V1, colorspace: cs_loop}\n "
10552+ " colorspaces:\n "
10553+ " - !<ColorSpace>\n "
10554+ " name: cs0\n "
10555+ " - !<ColorSpace>\n "
10556+ " name: cs_loop\n "
10557+ " from_scene_reference: !<DisplayViewTransform> {src: cs0, display: D1, view: V1}\n " ;
10558+
10559+ std::istringstream is;
10560+ is.str (CYCLIC_PROFILE );
10561+ OCIO ::ConstConfigRcPtr config;
10562+ OCIO_CHECK_NO_THROW (config = OCIO::Config::CreateFromStream (is));
10563+
10564+ OCIO_CHECK_THROW_WHAT (config->getProcessor (" cs0" , " cs_loop" ),
10565+ OCIO ::Exception,
10566+ " Cycle detected" );
10567+ }
10568+
0 commit comments