View Issue Details

IDProjectCategoryView StatusLast Update
0002317NoesisGUIC++ SDKpublic2022-05-03 19:15
Reportersteveh Assigned Tosfernandez  
PrioritynormalSeveritycrashReproducibilitysometimes
Status resolvedResolutionfixed 
Product Version3.1.4 
Target Version3.1.5Fixed in Version3.1.5 
Summary0002317: Crash when applying animation values in TimeManager::Tick
DescriptionHi guys,

I've spent quite a bit of time tracking down a crash. I've tracked it down to attempting to apply an animation value on a destroyed dependency object.

I've created a reproducible sample that I've attached as a ZIP to this bug report.

So what has happened?

1. In TimeManager::Tick it collates together all the animation values that it needs to apply to all dependency objects this frame. This is stored in a local HashMap on the stack which contains non-ref counted pointers
2. It then iterates through each AnimationValue and call DependencyObject::SetAnimation passing in the new value.
3. This invoked dependency property changed events.
4. If user code binds to this (e.g. the IsVisibleChanged event) we can run code which ultimately removes all references to dependency object and removes it
5. This can end up freeing the dependency objects that are currently stored as raw addresses in the stack that was created in step 1.
6. When it attempts to apply the animation value on the now deleted object it crashes.


So just to clarify, it's this bit of code in TimeManager::Tick which is broken:

-----------------
    // Update properties
    AnimationValues animationValues;
    for (Targets::Bucket& b: mTargets)
    {
        DependencyObject* target = b.key;
        const AnimatedProperties& properties = b.value.animatedProperties;

        EvaluateTarget(target, properties, animationValues, finishedClocks);
    }

    // Now that we are out of the targets loop, we can safely apply the new animation values
    ApplyAnimationValues(animationValues);

----------------

So if dependency objects that currently reside in the animationValues map are removed during ApplyAnimationValues it can crash. In my case this was caused by changing the Content from a DependencyPropertyChangedEvent which cleared out the ContentTemplate which destroyed the DependencyObjects created by that DataTemplate. Since this happened instantly, it ended up invalidating the AnimationValue map which had already taken a raw pointer to the DependencyObjects.


I've fixed this locally by adding all targets in the AnimationValue map to the "aliveTargets" vector:

    for (Targets::Bucket& b: mTargets)
    {
        DependencyObject* target = b.key;
        const AnimatedProperties& properties = b.value.animatedProperties;

        EvaluateTarget(target, properties, animationValues, finishedClocks);

        // Keep this target alive as it could be invalidated in ApplyAnimationValues
        aliveTargets.EmplaceBack().Reset(target); // [Sumo SPH 2022-03-31] - Keep these animation targets alive!
    }


I'm not sure if this is the best fix, but I've not been able to crash it after this change.

Cheers!
Steps To Reproduce1. Download my sample.
2. Extract to Noesis SDK
3. Build it
4. Click the button. When the button is collapsed it will trigger a DependencyPropertyChangedEvent which will lead to a crash when the content is changed.

Note: This does not seem to be 100% for some reason. If it doesn't break, just restart the sample. It'll break eventually.
TagsNo tags attached.
PlatformAny

Activities

steveh

steveh

2022-03-31 20:22

reporter  

AnimationCrashBug.zip (11,749 bytes)
jsantos

jsantos

2022-04-04 07:54

manager   ~0007884

Thanks for the report Steve, giving high-priority to this
steveh

steveh

2022-04-22 14:37

reporter   ~0007906

Hi guys, so in addition to the above change, one of our tech directors is investigating other stomps with other tools and has identified the following stomp (I've attached an image).

The change is the P4 diff. Unfortunately I didn't investigate this stomp so I don't have any more info on it. It looks like erasing the clock pointer inside RemoveMasterClock, and then we try to write mTarget to 0 on a now-erased clock. The fix is to zero out the target on the clock before it's erased from the mMasterClocks vector.
image.png (229,895 bytes)   
image.png (229,895 bytes)   
sfernandez

sfernandez

2022-04-22 16:18

manager   ~0007907

That change is not correct because there is another use of the RemoveMasterClock (in the method OnClockFinished) that requires later use of clock->mTarget.
The easiest way to fix it, before I analyze this better is to keep a reference to the clock in RemoveUnusedMasterClocks:

void TimeManager::RemoveUnusedMasterClocks(const ClocksSet& clocks)
{
    for (Clock* masterClock: clocks)
    {
        if (!IsClockUsed(masterClock))
        {
            Ptr<Clock> masterClockPtr(masterClock);
            RemoveMasterClock(masterClock);
            masterClock->mTarget = 0;
        }
    }
}


Could you try that?
steveh

steveh

2022-04-22 18:30

reporter   ~0007909

Hi Sergio, your suggestion works great. We've submitted that to our local branch, much appreciated.
sfernandez

sfernandez

2022-05-03 19:15

manager   ~0007916

Both issues solved in changeset r11317.

Issue History

Date Modified Username Field Change
2022-03-31 20:22 steveh New Issue
2022-03-31 20:22 steveh File Added: AnimationCrashBug.zip
2022-04-04 07:53 jsantos Assigned To => sfernandez
2022-04-04 07:53 jsantos Status new => assigned
2022-04-04 07:53 jsantos Product Version => 3.1.4
2022-04-04 07:53 jsantos Target Version => 3.1.5
2022-04-04 07:54 jsantos Note Added: 0007884
2022-04-22 14:37 steveh Note Added: 0007906
2022-04-22 14:37 steveh File Added: image.png
2022-04-22 16:18 sfernandez Status assigned => feedback
2022-04-22 16:18 sfernandez Note Added: 0007907
2022-04-22 18:30 steveh Note Added: 0007909
2022-04-22 18:30 steveh Status feedback => assigned
2022-05-03 19:15 sfernandez Status assigned => resolved
2022-05-03 19:15 sfernandez Resolution open => fixed
2022-05-03 19:15 sfernandez Fixed in Version => 3.1.5
2022-05-03 19:15 sfernandez Note Added: 0007916