Crash in ModMagnitudeCalculation when Snapshot refers to a GameplayEffect that has been unloaded

It seems that ModMagnitudeCalculation’s are capable of holding raw pointers to object CDOs without a UPROPERTY chain to keep those objects in memory. AddAggregatorMod creates FAggregators that hold raw pointers to the original GameplayEffect’s CDO (in particular the SourceTags and TargetTags members of FGameplayModifierInfo). If these FAggregators are snapshotted, and then the original CDO is removed, the ModMagnitudeCalculation will crash if it ever tries to access the snapshot copy of the FAggregators.

Is there something we can change to make ModMagnitudeCalculations keep the target CDOs around until they are no longer needed? Perhaps adding some UPROPERTY(…) values to FGameplayEffectAttributeCaptureSpec?

Steps to Reproduce

  1. Create a GameplayEffect MyAttributeChanger that modifies an attribute MyAttribute and with a short duration.
  2. Create a ModifierMagnitudeCalculation MyMMC that captures MyAttribute as a snapshot from the target.
    1. Implement CalculateBaseMagnitude in MyMMC by calling GetCapturedAttributeMagnitudeon MyAttribute using the passed in Spec
  3. Create a GameplayEffect MyAttributeWatcher that uses MyMMC to apply an attribute change to MyAttribute (for example, add the result). Set duration to infinite.
  4. Setup MyAttributeChanger so that is in a GameFeaturePlugin or other pattern that allows dynamic loading and unloading
  5. Create logic that will cause the following in order:
    1. Load MyAttributeChanger class
    2. Apply MyAttributeChanger to a pawn with an AbilitySystem that has MyAttribute
    3. Apply MyAttributeWatcher to the same pawn, store FActiveGameplayEffectHandle that this returns
    4. Allow MyAttributeChanger effect to expire and fall off
    5. Unload MyAttributeChanger class
    6. Run the Garbage collector so the CDO of MyAttributeChanger is unloaded.
    7. Call UpdateActiveGameplayEffectSetByCallerMagnitude using the previously stored FActiveGameplayEffectHandle (using any SetByCaller value, it doesn’t matter)

The chain of events that triggers the crash:

  1. MyAttributeWatcher calls UpdateAllAggregatorModMagnitudes.
  2. The MMC blueprint calls UGameplayModMagnitudeCalculation::GetCapturedAttributeMagnitude
  3. This walks all FAggregators and calls FAggregator::Evaluate.
  4. This calls through a few levels, but eventually calls FAggregatorMod::UpdateQualifies
  5. UpdateQualifies wants to check SourceTagReqs and TargetTagReqs, but these are RAW POINTERs to the CDO of MyAttributeChanger, which has been unloaded.

I’ve recently started looking into a similar issue involving the Source and Target TagReqs, so this repro will be very helpful—thank you. Unfortunately, since aggregators aren’t USTRUCTs, there’s no straightforward way to ensure the GameplayEffect is retained in this scenario.

Your suggestion of adding something to the FGameplayEffectAttributeCaptureSpec is a reasonable workaround for now. That said, you’d need to iterate through all the modifiers in the captured aggregator and store the referenced GameplayEffects (or their specs) in something like a UPROPERTY-marked TArray.

I’m actively working on a solution, but it’s part of a larger task and likely won’t be available until that broader effort is complete.