Skip to content

Commit

Permalink
Fix method dispatch failure when args fail to deserialize for ETW ver…
Browse files Browse the repository at this point in the history
…bose logging

Fixes microsoft#841
  • Loading branch information
AArnott committed Sep 9, 2022
1 parent 39a0710 commit 9f5945f
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 12 deletions.
42 changes: 31 additions & 11 deletions src/StreamJsonRpc/JsonRpcEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ internal sealed class JsonRpcEventSource : EventSource
/// <summary>
/// A flag indicating whether to force tracing to be on.
/// </summary>
private static readonly bool AlwaysOn = Environment.GetEnvironmentVariable("StreamJsonRpc_TestWithEventSource") == "1";
private static readonly bool AlwaysOn = true || Environment.GetEnvironmentVariable("StreamJsonRpc_TestWithEventSource") == "1";

/// <summary>
/// Initializes a new instance of the <see cref="JsonRpcEventSource"/> class.
Expand Down 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 9f5945f

Please sign in to comment.