Table of Contents

NBMsgPack036: Async converters should not reuse readers after returning them

Custom converters (classes that derive from MessagePackConverter<T>) that override the ReadAsync method should not reuse a MessagePackReader or MessagePackStreamingReader after returning it via ReturnReader.

Example violation

In the following example, the MessagePackStreamingReader is returned before the method is done using it.

public override async ValueTask<SomeCustomType?> ReadAsync(MessagePackAsyncReader reader, SerializationContext context)
{
    MessagePackStreamingReader streamingReader = reader.CreateStreamingReader();

    int count;
    while (streamingReader.TryReadArrayHeader(out count).NeedsMoreBytes())
    {
        streamingReader = new(await streamingReader.FetchMoreBytesAsync());
    }

    if (count != 1)
    {
        throw new MessagePackSerializationException();
    }

    reader.ReturnReader(ref streamingReader); // streamingReader returned here

    int seedCount;
    while (streamingReader.TryRead(out seedCount).NeedsMoreBytes()) // OOPS: streamingReader reused here
    {
        streamingReader = new(await streamingReader.FetchMoreBytesAsync());
    }

    return new SomeCustomType(seedCount);
}

Resolution

The fix is simply to move the ReturnReader call to a point after the method is done using the reader.

public override async ValueTask<SomeCustomType?> ReadAsync(MessagePackAsyncReader reader, SerializationContext context)
{
    MessagePackStreamingReader streamingReader = reader.CreateStreamingReader();

    int count;
    while (streamingReader.TryReadArrayHeader(out count).NeedsMoreBytes())
    {
        streamingReader = new(await streamingReader.FetchMoreBytesAsync());
    }

    if (count != 1)
    {
        throw new MessagePackSerializationException();
    }

    int seedCount;
    while (streamingReader.TryRead(out seedCount).NeedsMoreBytes())
    {
        streamingReader = new(await streamingReader.FetchMoreBytesAsync());
    }

    reader.ReturnReader(ref streamingReader);
    return new SomeCustomType(seedCount);
}