Skip to content

LoadStateAsync throws instead of returning false when a saved command's constructor throws or its type is an open generic #102

Description

@matt-edmondson

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions