Skip to content

Commit

Permalink
Merge pull request #842 from AArnott/fix841
Browse files Browse the repository at this point in the history
Fix method dispatch failure when args fail to deserialize for ETW verbose logging
  • Loading branch information
AArnott authored Sep 9, 2022
2 parents 39a0710 + c0a7bbd commit cb35512
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 11 deletions.
40 changes: 30 additions & 10 deletions src/StreamJsonRpc/JsonRpcEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,22 @@ internal static string GetArgumentsString(JsonRpcRequest request)
stringBuilder.Append('[');
for (int i = 0; i < request.ArgumentCount; ++i)
{
if (request.TryGetArgumentByNameOrIndex(null, i, null, out object? value))
bool formatted = false;
try
{
Format(stringBuilder, value);
stringBuilder.Append(", ");
if (request.TryGetArgumentByNameOrIndex(null, i, null, out object? value))
{
Format(stringBuilder, value);
stringBuilder.Append(", ");
formatted = true;
}
}
else
catch
{
// Swallow exceptions when deserializing args for ETW logging. It's never worth causing a functional failure.
}

if (!formatted)
{
stringBuilder.Append("<unformattable>");
}
Expand All @@ -317,14 +327,24 @@ internal static string GetArgumentsString(JsonRpcRequest request)
stringBuilder.Append('{');
foreach (string key in request.ArgumentNames)
{
if (request.TryGetArgumentByNameOrIndex(key, -1, null, out object? value))
bool formatted = false;
try
{
stringBuilder.Append(key);
stringBuilder.Append(": ");
Format(stringBuilder, value);
stringBuilder.Append(", ");
if (request.TryGetArgumentByNameOrIndex(key, -1, null, out object? value))
{
stringBuilder.Append(key);
stringBuilder.Append(": ");
Format(stringBuilder, value);
stringBuilder.Append(", ");
formatted = true;
}
}
else
catch
{
// Swallow exceptions when deserializing args for ETW logging. It's never worth causing a functional failure.
}

if (!formatted)
{
stringBuilder.Append("<unformattable>");
}
Expand Down
59 changes: 58 additions & 1 deletion test/StreamJsonRpc.Tests/JsonRpcMessagePackLengthTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ internal interface IMessagePackServer
Task ProgressUnionType(IProgress<UnionBaseClass> progress, CancellationToken cancellationToken);

IAsyncEnumerable<UnionBaseClass> GetAsyncEnumerableOfUnionType(CancellationToken cancellationToken);

Task<bool> IsExtensionArgNonNull(CustomExtensionType extensionValue);
}

[Fact]
Expand Down Expand Up @@ -363,14 +365,33 @@ public async Task UnionType_AsAsyncEnumerableTypeArgument()
Assert.IsType<UnionDerivedClass>(actualItem);
}

/// <summary>
/// Verifies that an argument that cannot be deserialized by the msgpack primitive formatter will not cause a failure.
/// </summary>
/// <remarks>
/// <para>This is a regression test for <see href="https://github.com/microsoft/vs-streamjsonrpc/issues/841">a bug</see> where
/// verbose ETW tracing would fail to deserialize arguments with the primitive formatter that deserialize just fine for the actual method dispatch.</para>
/// <para>This test is effective because custom msgpack extensions cause the <see cref="PrimitiveObjectFormatter"/> to throw an exception when deserializing.</para>
/// </remarks>
[Theory, PairwiseData]
public async Task VerboseLoggingDoesNotFailWhenArgsDoNotDeserializePrimitively(bool namedArguments)
{
var server = new MessagePackServer();
this.serverRpc.AllowModificationWhileListening = true;
this.serverRpc.AddLocalRpcTarget(server);
var clientProxy = this.clientRpc.Attach<IMessagePackServer>(new JsonRpcProxyOptions { ServerRequiresNamedArguments = namedArguments });

Assert.True(await clientProxy.IsExtensionArgNonNull(new CustomExtensionType()));
}

protected override void InitializeFormattersAndHandlers(bool controlledFlushingClient)
{
this.serverMessageFormatter = new MessagePackFormatter();
this.clientMessageFormatter = new MessagePackFormatter();

var options = MessagePackFormatter.DefaultUserDataSerializationOptions
.WithResolver(CompositeResolver.Create(
new IMessagePackFormatter[] { new UnserializableTypeFormatter(), new TypeThrowsWhenDeserializedFormatter() },
new IMessagePackFormatter[] { new UnserializableTypeFormatter(), new TypeThrowsWhenDeserializedFormatter(), new CustomExtensionFormatter() },
new IFormatterResolver[] { StandardResolverAllowPrivate.Instance }));
((MessagePackFormatter)this.serverMessageFormatter).SetMessagePackSerializerOptions(options);
((MessagePackFormatter)this.clientMessageFormatter).SetMessagePackSerializerOptions(options);
Expand All @@ -392,6 +413,40 @@ public class UnionDerivedClass : UnionBaseClass
{
}

internal class CustomExtensionType
{
}

private class CustomExtensionFormatter : IMessagePackFormatter<CustomExtensionType?>
{
public CustomExtensionType? Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
if (reader.TryReadNil())
{
return null;
}

if (reader.ReadExtensionFormat() is { Header: { TypeCode: 1, Length: 0 } })
{
return new();
}

throw new Exception("Unexpected extension header.");
}

public void Serialize(ref MessagePackWriter writer, CustomExtensionType? value, MessagePackSerializerOptions options)
{
if (value is null)
{
writer.WriteNil();
}
else
{
writer.WriteExtensionFormat(new ExtensionResult(1, default(Memory<byte>)));
}
}
}

private class UnserializableTypeFormatter : IMessagePackFormatter<CustomSerializedType>
{
public CustomSerializedType Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
Expand Down Expand Up @@ -448,6 +503,8 @@ public async IAsyncEnumerable<UnionBaseClass> GetAsyncEnumerableOfUnionType([Enu
await Task.Yield();
yield return new UnionDerivedClass();
}

public Task<bool> IsExtensionArgNonNull(CustomExtensionType extensionValue) => Task.FromResult(extensionValue is not null);
}

private class DelayedFlushingHandler : LengthHeaderMessageHandler, IControlledFlushHandler
Expand Down

0 comments on commit cb35512

Please sign in to comment.