Commit: afa3c66668debdc5b31707dbb8da39c5460acebe Author: Sybren A. Stüvel Date: Tue Jul 6 15:36:27 2021 +0300 Branches: lineart-shadow https://developer.blender.org/rBafa3c66668debdc5b31707dbb8da39c5460acebe
Fix T89435: Reordering FCurves can cause crash or corruption Correctly reset `prev` and `next` pointers of action group FCurves when separating them into distinct `ListBase`s per `bActionGroup`. These `NULL` pointers are necessary to temporarily demarcate the start & end of the `bActionGroup::channels` list. Having them still point to other FCurves caused ordering issues when moving curves towards the start/end of a group. This commit corrects the above issue and adds versioning code to rectify any ordering issues that may have been caused. For this purpose the `BKE_action_groups_reconstruct()` function is rewritten to avoid relying on the `bAction::curves` list order or `prev` link integrity. Differential Revision: https://developer.blender.org/D11811 =================================================================== M source/blender/blenkernel/BKE_blender_version.h M source/blender/blenkernel/CMakeLists.txt M source/blender/blenkernel/intern/action.c A source/blender/blenkernel/intern/action_test.cc M source/blender/blenloader/intern/versioning_300.c M source/blender/editors/animation/anim_channels_edit.c =================================================================== diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 9d286b94b04..30f6ad7475d 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,7 +39,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 8 +#define BLENDER_FILE_SUBVERSION 9 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index a0aee552759..2c25b940578 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -765,6 +765,7 @@ add_dependencies(bf_blenkernel bf_dna) if(WITH_GTESTS) set(TEST_SRC + intern/action_test.cc intern/armature_test.cc intern/cryptomatte_test.cc intern/fcurve_test.cc diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index 13ca5ecf23c..d55f023d209 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -497,9 +497,8 @@ void action_groups_add_channel(bAction *act, bActionGroup *agrp, FCurve *fcurve) } /* Reconstruct group channel pointers. - * Assumes that the channels are still in the proper order, i.e. that channels of the same group - * are adjacent in the act->channels list. It also assumes that the groups - * referred to by the FCurves are already in act->groups. + * Assumes that the groups referred to by the FCurves are already in act->groups. + * Reorders the main channel list to match group order. */ void BKE_action_groups_reconstruct(bAction *act) { @@ -514,23 +513,30 @@ void BKE_action_groups_reconstruct(bAction *act) BLI_listbase_clear(&group->channels); } - bActionGroup *grp; - bActionGroup *last_grp = NULL; - LISTBASE_FOREACH (FCurve *, fcurve, &act->curves) { - if (fcurve->grp == NULL) { - continue; - } + /* Sort the channels into the group lists, destroying the act->curves list. */ + ListBase ungrouped = {NULL, NULL}; - grp = fcurve->grp; - if (last_grp != grp) { - /* If this is the first time we see this group, this must be the first channel. */ - grp->channels.first = fcurve; + LISTBASE_FOREACH_MUTABLE (FCurve *, fcurve, &act->curves) { + if (fcurve->grp) { + BLI_assert(BLI_findindex(&act->groups, fcurve->grp) >= 0); + + BLI_addtail(&fcurve->grp->channels, fcurve); + } + else { + BLI_addtail(&ungrouped, fcurve); } + } + + /* Recombine into the main list. */ + BLI_listbase_clear(&act->curves); - /* This is the last channel, until it's overwritten by a later iteration. */ - grp->channels.last = fcurve; - last_grp = grp; + LISTBASE_FOREACH (bActionGroup *, group, &act->groups) { + /* Copy the list header to preserve the pointers in the group. */ + ListBase tmp = group->channels; + BLI_movelisttolist(&act->curves, &tmp); } + + BLI_movelisttolist(&act->curves, &ungrouped); } /* Remove the given channel from all groups */ diff --git a/source/blender/blenkernel/intern/action_test.cc b/source/blender/blenkernel/intern/action_test.cc new file mode 100644 index 00000000000..cd8751ec358 --- /dev/null +++ b/source/blender/blenkernel/intern/action_test.cc @@ -0,0 +1,144 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation + * All rights reserved. + */ + +#include "BKE_action.h" + +#include "DNA_action_types.h" +#include "DNA_anim_types.h" + +#include "BLI_listbase.h" + +#include "testing/testing.h" + +namespace blender::bke::tests { + +TEST(action_groups, ReconstructGroupsWithReordering) +{ + // Construct an Action with three groups. + bAction action = {0}; + FCurve groupAcurve1 = {0}; + FCurve groupAcurve2 = {0}; + FCurve groupBcurve1 = {0}; + FCurve groupBcurve2 = {0}; + FCurve groupBcurve3 = {0}; + // Group C has no curves intentionally. + FCurve groupDcurve1 = {0}; + FCurve groupDcurve2 = {0}; + + groupAcurve1.rna_path = (char *)"groupAcurve1"; + groupAcurve2.rna_path = (char *)"groupAcurve2"; + groupBcurve1.rna_path = (char *)"groupBcurve1"; + groupBcurve2.rna_path = (char *)"groupBcurve2"; + groupDcurve1.rna_path = (char *)"groupDcurve1"; + groupBcurve3.rna_path = (char *)"groupBcurve3"; + groupDcurve2.rna_path = (char *)"groupDcurve2"; + + BLI_addtail(&action.curves, &groupAcurve1); + BLI_addtail(&action.curves, &groupAcurve2); + BLI_addtail(&action.curves, &groupBcurve1); + BLI_addtail(&action.curves, &groupBcurve2); + BLI_addtail(&action.curves, &groupDcurve1); + BLI_addtail(&action.curves, &groupBcurve3); // <-- The error that should be corrected. + BLI_addtail(&action.curves, &groupDcurve2); + + // Introduce another error type, by changing some `prev` pointers. + groupBcurve1.prev = NULL; + groupBcurve3.prev = &groupBcurve2; + groupDcurve1.prev = &groupBcurve3; + + bActionGroup groupA = {0}; + bActionGroup groupB = {0}; + bActionGroup groupC = {0}; + bActionGroup groupD = {0}; + strcpy(groupA.name, "groupA"); + strcpy(groupB.name, "groupB"); + strcpy(groupC.name, "groupC"); + strcpy(groupD.name, "groupD"); + + BLI_addtail(&action.groups, &groupA); + BLI_addtail(&action.groups, &groupB); + BLI_addtail(&action.groups, &groupC); + BLI_addtail(&action.groups, &groupD); + + groupAcurve1.grp = &groupA; + groupAcurve2.grp = &groupA; + groupBcurve1.grp = &groupB; + groupBcurve2.grp = &groupB; + groupBcurve3.grp = &groupB; + groupDcurve1.grp = &groupD; + groupDcurve2.grp = &groupD; + + groupA.channels.first = &groupAcurve1; + groupA.channels.last = &groupAcurve2; + groupB.channels.first = &groupBcurve1; + groupB.channels.last = &groupBcurve3; // The last channel in group B, after group C curve 1. + groupD.channels.first = &groupDcurve1; + groupD.channels.last = &groupDcurve2; + + EXPECT_EQ(groupA.channels.first, &groupAcurve1); + EXPECT_EQ(groupA.channels.last, &groupAcurve2); + EXPECT_EQ(groupB.channels.first, &groupBcurve1); + EXPECT_EQ(groupB.channels.last, &groupBcurve3); + EXPECT_EQ(groupC.channels.first, nullptr); + EXPECT_EQ(groupC.channels.last, nullptr); + EXPECT_EQ(groupD.channels.first, &groupDcurve1); + EXPECT_EQ(groupD.channels.last, &groupDcurve2); + + BKE_action_groups_reconstruct(&action); + + EXPECT_EQ(action.curves.first, &groupAcurve1); + EXPECT_EQ(action.curves.last, &groupDcurve2); + + EXPECT_EQ(groupA.prev, nullptr); + EXPECT_EQ(groupB.prev, &groupA); + EXPECT_EQ(groupC.prev, &groupB); + EXPECT_EQ(groupD.prev, &groupC); + + EXPECT_EQ(groupA.next, &groupB); + EXPECT_EQ(groupB.next, &groupC); + EXPECT_EQ(groupC.next, &groupD); + EXPECT_EQ(groupD.next, nullptr); + + EXPECT_EQ(groupA.channels.first, &groupAcurve1); + EXPECT_EQ(groupA.channels.last, &groupAcurve2); + EXPECT_EQ(groupB.channels.first, &groupBcurve1); + EXPECT_EQ(groupB.channels.last, &groupBcurve3); + EXPECT_EQ(groupC.channels.first, nullptr); + EXPECT_EQ(groupC.channels.last, nullptr); + EXPECT_EQ(groupD.channels.first, &groupDcurve1); + EXPECT_EQ(groupD.channels.last, &groupDcurve2); + + EXPECT_EQ(groupAcurve1.prev, nullptr); + EXPECT_EQ(groupAcurve2.prev, &groupAcurve1); + EXPECT_EQ(groupBcurve1.prev, &groupAcurve2); + EXPECT_EQ(groupBcurve2.prev, &groupBcurve1); + EXPECT_EQ(groupBcurve3.prev, &groupBcurve2); + EXPECT_EQ(groupDcurve1.prev, &groupBcurve3); + EXPECT_EQ(groupDcurve2.prev, &groupDcurve1); + + EXPECT_EQ(groupAcurve1.next, &groupAcurve2); + EXPECT_EQ(groupAcurve2.next, &groupBcurve1); + EXPECT_EQ(groupBcurve1.next, &groupBcurve2); + EXPECT_EQ(groupBcurve2.next, &groupBcurve3); + EXPECT_EQ(groupBcurve3.next, &groupDcurve1); + EXPECT_EQ(groupDcurve1.next, &groupDcurve2); + EXPECT_EQ(groupDcurve2.next, nullptr); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 80a983f9983..b451bcb1e22 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -37,6 +37,7 @@ #include "DNA_modifier_types.h" #include "DNA_text_types.h" +#include "BKE_action.h" #include "BKE_animsys.h" #include "BKE_collection.h" #include "BKE_fcurve_driver.h" @@ -501,17 +502,13 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } - /** - * Versioning code until next subversion bump goes here. - * - * \note Be sure to check when bumping the version: - * - "versioning_userdef.c", #blo_do_versions_userdef - * - "versioning_userdef.c", #do_versions_theme - * - * \note Keep this message at the bottom of the function. - */ - { - /* Keep this block, even when empty. */ + if (!MAIN_VERSION_ATLEAST(bmain, 300, 9)) { + /* Fix a bug where reordering FCurves and bActionGroups could cause some corruption. Just + * reconstruct all the action groups & ensure that the FCurves of a group are continuously + * stored (i.e. not mixed with other groups) to be sure. See T89435. */ + LISTBASE_FOREACH (bAction *, act, &bmain->actions) { + BKE_action_groups_reconstruct(act); + } FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_GEOMETRY) { @@ -524,4 +521,17 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED( @@ Diff output truncated at 10240 characters. @@ _______________________________________________ Bf-blender-cvs mailing list Bf-blender-cvs@blender.org List details, subscription details or unsubscribe: https://lists.blender.org/mailman/listinfo/bf-blender-cvs