What's wrong
The fix for #93 (PR #99) made LoadStateAsync report false for every unloadable saved command. Two exceptions from Activator.CreateInstance still get through:
JsonUndoRedoSerializer.ConvertFromSerializableCommand (UndoRedo/Services/JsonUndoRedoSerializer.cs:~158-170) catches only MissingMethodException around Activator.CreateInstance(commandType).
UndoRedoService.LoadStateAsync (UndoRedo/Services/UndoRedoService.cs:351) catches only JsonException, InvalidOperationException and NotSupportedException.
So these escape to the caller:
TargetInvocationException, when the parameterless constructor itself throws
ArgumentException, when the saved type name resolves to an open generic (ContainsGenericParameters)
TypeInitializationException and MemberAccessException would escape the same way.
Repro (verified against the built library)
public class ThrowingCtorCmd : BaseCommand, ISerializableCommand
{
public ThrowingCtorCmd() : base(ChangeType.Modify, []) => throw new InvalidDataException("needs context");
public override string Description => "t";
public override void Execute() { }
public override void Undo() { }
public string SerializeData() => "1";
public void DeserializeData(string d) { }
}
Hand-craft saved state with one command whose "type" is the assembly-qualified name of ThrowingCtorCmd, or of an open GenericCmd<> with the same shape, and pass it to LoadStateAsync.
Why it matters
Command constructors that depend on services, configuration or static state are common. Opening a file saved by another build, or after such a dependency changes, crashes the caller instead of producing the documented "failed load" result.
Suggested fix
Around Activator.CreateInstance, catch MissingMethodException or TargetInvocationException or ArgumentException or MemberAccessException or TypeInitializationException, or everything except OperationCanceledException, as the DeserializeData block just below already does. Wrap the exception in InvalidOperationException.
Acceptance: add a throwing constructor and an open generic type to the #93 data-driven load-failure tests. Both return false and leave the stack unchanged.
What's wrong
The fix for #93 (PR #99) made
LoadStateAsyncreportfalsefor every unloadable saved command. Two exceptions fromActivator.CreateInstancestill get through:JsonUndoRedoSerializer.ConvertFromSerializableCommand(UndoRedo/Services/JsonUndoRedoSerializer.cs:~158-170) catches onlyMissingMethodExceptionaroundActivator.CreateInstance(commandType).UndoRedoService.LoadStateAsync(UndoRedo/Services/UndoRedoService.cs:351) catches onlyJsonException,InvalidOperationExceptionandNotSupportedException.So these escape to the caller:
TargetInvocationException, when the parameterless constructor itself throwsArgumentException, when the saved type name resolves to an open generic (ContainsGenericParameters)TypeInitializationExceptionandMemberAccessExceptionwould escape the same way.Repro (verified against the built library)
Hand-craft saved state with one command whose
"type"is the assembly-qualified name ofThrowingCtorCmd, or of an openGenericCmd<>with the same shape, and pass it toLoadStateAsync.TargetInvocationException(orArgumentException: Cannot create an instance of GenericCmd1[T] because Type.ContainsGenericParameters is true) escapesLoadStateAsync`.false, and the live history is kept. This is what the LoadStateAsync throws FileLoadException/InvalidCastException/FormatException on a bad command "type" or data instead of returning false #93 fix promises, and what already happens for an abstract type or one with only a private constructor.Why it matters
Command constructors that depend on services, configuration or static state are common. Opening a file saved by another build, or after such a dependency changes, crashes the caller instead of producing the documented "failed load" result.
Suggested fix
Around
Activator.CreateInstance, catchMissingMethodException or TargetInvocationException or ArgumentException or MemberAccessException or TypeInitializationException, or everything exceptOperationCanceledException, as theDeserializeDatablock just below already does. Wrap the exception inInvalidOperationException.Acceptance: add a throwing constructor and an open generic type to the #93 data-driven load-failure tests. Both return
falseand leave the stack unchanged.