diff --git a/src/opentimelineio/algo/editAlgorithm.cpp b/src/opentimelineio/algo/editAlgorithm.cpp index 55e41f226..7f653f523 100644 --- a/src/opentimelineio/algo/editAlgorithm.cpp +++ b/src/opentimelineio/algo/editAlgorithm.cpp @@ -181,9 +181,21 @@ overwrite( composition->insert_child(insert_index, item); if (!isEqual(second_duration.value(), 0.0)) { - auto second_item = dynamic_cast(items.front()->clone()); - trimmed_range = second_item->trimmed_range(); - source_range = TimeRange( + auto cloned_item = items.front()->clone(error_status); + if (!cloned_item) + { + // error_status was already set within clone() + return; + } + auto second_item = dynamic_cast(cloned_item); + if (!second_item) + { + if (error_status) + *error_status = ErrorStatus::TYPE_MISMATCH; + return; + } + trimmed_range = second_item->trimmed_range(); + source_range = TimeRange( trimmed_range.start_time() + first_duration + range.duration(), second_duration); @@ -362,7 +374,19 @@ insert( // Clone the item for the second partially overwritten item. if (!isEqual(second_source_range.duration().value(), 0.0)) { - auto second_item = dynamic_cast(item->clone()); + auto cloned_item = item->clone(error_status); + if (!cloned_item) + { + // error_status was already set within clone() + return; + } + auto second_item = dynamic_cast(cloned_item); + if (!second_item) + { + if (error_status) + *error_status = ErrorStatus::TYPE_MISMATCH; + return; + } second_item->set_source_range(second_source_range); composition->insert_child(insert_index + 1, second_item); } @@ -530,7 +554,19 @@ slice( item->set_source_range(first_source_range); // Clone the item for the second slice. - auto second_item = dynamic_cast(item->clone()); + auto cloned_item = item->clone(error_status); + if (!cloned_item) + { + // error_status was already set within clone() + return; + } + auto second_item = dynamic_cast(cloned_item); + if (!second_item) + { + if (error_status) + *error_status = ErrorStatus::TYPE_MISMATCH; + return; + } const TimeRange second_source_range( first_source_range.start_time() + first_source_range.duration(), range.duration() - first_source_range.duration()); @@ -793,7 +829,19 @@ fill( case ReferencePoint::Sequence: { RationalTime start_time = clip_range.start_time(); const RationalTime gap_start_time = gap_range.start_time(); - auto track_item = dynamic_cast(item->clone()); + auto cloned_item = item->clone(error_status); + if (!cloned_item) + { + // error_status was already set within clone() + return; + } + auto track_item = dynamic_cast(cloned_item); + if (!track_item) + { + if (error_status) + *error_status = ErrorStatus::TYPE_MISMATCH; + return; + } // Check if start time is less than gap's start time (trim it if so) if (start_time < gap_start_time) diff --git a/tests/test_editAlgorithm.cpp b/tests/test_editAlgorithm.cpp index 30b82858d..75dae4362 100644 --- a/tests/test_editAlgorithm.cpp +++ b/tests/test_editAlgorithm.cpp @@ -2131,6 +2131,125 @@ main(int argc, char** argv) TimeRange(RationalTime(0.0, 24.0), RationalTime(10.0, 24.0)) }); }); + tests.add_test("regression: crash in slice", [] { + SerializableObject::Retainer big_clip = new Clip( + "big clip", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0))); + big_clip->metadata()["cycle"] = big_clip; + + SerializableObject::Retainer small_clip = new Clip( + "small clip", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(5.0, 24.0))); + small_clip->metadata()["cycle"] = small_clip; + + SerializableObject::Retainer track = new Track(); + track->append_child(big_clip); + + OTIO_NS::ErrorStatus error_status; + + algo::slice(track, RationalTime(12.0, 24.0), false, &error_status); + + assert(is_error(error_status)); + assert(error_status.outcome == OTIO_NS::ErrorStatus::TYPE_MISMATCH); + }); + + tests.add_test("regression: crash in overwrite", [] { + SerializableObject::Retainer big_clip = new Clip( + "big clip", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0))); + big_clip->metadata()["cycle"] = big_clip; + + SerializableObject::Retainer small_clip = new Clip( + "small clip", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(5.0, 24.0))); + small_clip->metadata()["cycle"] = small_clip; + + SerializableObject::Retainer track = new Track(); + track->append_child(big_clip); + + OTIO_NS::ErrorStatus error_status; + + algo::overwrite( + small_clip, + track, + TimeRange(RationalTime(0.0, 24.0), RationalTime(12.0, 24.0)), + true, + nullptr, + &error_status); + + assert(is_error(error_status)); + assert(error_status.outcome == OTIO_NS::ErrorStatus::TYPE_MISMATCH); + }); + + tests.add_test("regression: crash in insert", [] { + SerializableObject::Retainer big_clip = new Clip( + "big clip", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0))); + big_clip->metadata()["cycle"] = big_clip; + + SerializableObject::Retainer small_clip = new Clip( + "small clip", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(5.0, 24.0))); + small_clip->metadata()["cycle"] = small_clip; + + SerializableObject::Retainer track = new Track(); + track->append_child(big_clip); + + OTIO_NS::ErrorStatus error_status; + + algo::insert( + small_clip, + track, + RationalTime(12.0, 24.0), + true, + nullptr, + &error_status); + + assert(is_error(error_status)); + assert(error_status.outcome == OTIO_NS::ErrorStatus::TYPE_MISMATCH); + }); + + tests.add_test("regression: crash in fill", [] { + SerializableObject::Retainer big_clip = new Clip( + "big clip", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0))); + big_clip->metadata()["cycle"] = big_clip; + + SerializableObject::Retainer small_clip = new Clip( + "small clip", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(5.0, 24.0))); + small_clip->metadata()["cycle"] = small_clip; + + SerializableObject::Retainer gap = new Gap( + TimeRange(RationalTime(0.0, 24.0), RationalTime(20.0, 24.0)), + "gap"); + + SerializableObject::Retainer track = new Track(); + track->append_child(small_clip); + track->append_child(gap); + track->append_child(small_clip); + + OTIO_NS::ErrorStatus error_status; + + algo::fill( + big_clip, + track, + RationalTime(12.0, 24.0), + ReferencePoint::Sequence, + &error_status); + + assert(is_error(error_status)); + assert(error_status.outcome == OTIO_NS::ErrorStatus::TYPE_MISMATCH); + }); + tests.run(argc, argv); return 0; } diff --git a/tests/test_serialization.cpp b/tests/test_serialization.cpp index 7ff13f76c..e5245459f 100644 --- a/tests/test_serialization.cpp +++ b/tests/test_serialization.cpp @@ -110,6 +110,39 @@ main(int argc, char** argv) })CONTENT"); }); + tests.add_test("clone", [] { + auto json = R"CONTENT({ + "OTIO_SCHEMA": "SerializableObjectWithMetadata.1", + "metadata": { + "a": 1, + "b": "two", + "c": [ + 3, + 4, + 5 + ], + "d": { + "hello": "nested" + } + }, + "name": "" +})CONTENT"; + auto original = SerializableObjectWithMetadata::from_json_string(json); + auto cloned = original->clone(); + assert(cloned != nullptr); + OTIO_NS::ErrorStatus err; + auto cloned_json = cloned->to_json_string(&err, {}, 2); + assertFalse(is_error(err)); + assertEqual(json, cloned_json.c_str()); + }); + + tests.add_test("clone with cycle returns nullptr", [] { + auto original = new SerializableObjectWithMetadata(); + original->metadata()["cycle"] = original; + auto cloned = original->clone(); + assert(cloned == nullptr); + }); + tests.run(argc, argv); return 0; }