Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions samples/ErrorProne.Samples/CoreAnalyzers/DemoEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@ public void MyEvent5(string str)
WriteEvent(5, str, 42);
}

// Properties should NOT be treated as event methods (ERP042 must not warn here).
private int SomeValue { get; set; }

private string Name { get; set; }

// Non-void returning methods are not implicit event methods (ERP042 must not warn here).
private int Compute() => 42;
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,69 @@ public sealed class DemoEventSource : System.Diagnostics.Tracing.EventSource
await VerifyCS.VerifyAsync(code);
}

[Test]
public async Task No_Warn_On_Non_Void_Method()
{
// Only void-returning methods are implicit event methods, so non-void methods must not be flagged.
string code = @"
[System.Diagnostics.Tracing.EventSource(Name = ""Demo"")]
public sealed class DemoEventSource : System.Diagnostics.Tracing.EventSource
{
private int Compute() => 42;

private string GetName() { return string.Empty; }

[System.Diagnostics.Tracing.Event(1)]
public void AppStarted(string message) => WriteEvent(1, message);
}";

await VerifyCS.VerifyAsync(code);
}

[Test]
public async Task No_Warn_When_NonEvent_Method_Precedes_Implicit_Event()
{
// A [NonEvent] method must not shift the inferred ordinal id of the implicit event method.
// Here the implicit event method is the 2nd ordinary method but the 1st actual event, so its id is 1
// and WriteEvent(1, ...) must match.
string code = @"
[System.Diagnostics.Tracing.EventSource(Name = ""Demo"")]
public sealed class DemoEventSource : System.Diagnostics.Tracing.EventSource
{
[System.Diagnostics.Tracing.NonEvent]
public void Helper() {}

public void AppStarted(string message) => WriteEvent(1, message);
}";

await VerifyCS.VerifyAsync(code);
}

[Test]
public async Task No_Warn_On_Properties()
{
string code = @"
[System.Diagnostics.Tracing.EventSource(Name = ""Demo"")]
public sealed class DemoEventSource : System.Diagnostics.Tracing.EventSource
{
private int AutoProperty { get; set; }

private string ExpressionProperty => string.Empty;

private int _field;
private int FullProperty
{
get => _field;
set => _field = value;
}

[System.Diagnostics.Tracing.Event(1)]
public void AppStarted(string message) => WriteEvent(1, message);
}";

await VerifyCS.VerifyAsync(code);
}

[Test]
public async Task Warn_On_Count_Mismatch_Core()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,15 @@ private void AnalyzeMethodBody(OperationAnalysisContext context)
if (eventAttribute == null)
{
// From the docs: "Any instance, non-virtual, void returning method defined in an event source class is by default an ETW event method."
if (!method.IsStatic && !method.IsVirtual && method.ReturnsVoid && !method.IsConstructor() && method.MethodKind != MethodKind.Destructor)
if (IsImplicitEventMethodCandidate(method, compilation))
{
// In this case the Id is inferred.
// "Implicitly: by the ordinal number of the method in the class (thus the first method in the class is 1, second 2 …)"
var methods = method.ContainingType.GetMembers().OfType<IMethodSymbol>().Where(m => !m.IsStatic && !m.IsVirtual && m.ReturnsVoid).ToList();

// The ordinal list must use the same eligibility rules so that [NonEvent] (and other non-event) methods
// don't shift the inferred index of real event methods.
var methods = method.ContainingType.GetMembers().OfType<IMethodSymbol>()
.Where(m => IsImplicitEventMethodCandidate(m, compilation)).ToList();

var index = methods.IndexOf(method) + 1;
return new EventMethodInfo(EventId: index, MethodSymbol: method);
}
Expand All @@ -314,6 +317,23 @@ private void AnalyzeMethodBody(OperationAnalysisContext context)
return new EventMethodInfo(eventId, method);
}

/// <summary>
/// Returns true if the method can be an implicit ETW event method (i.e. one without an explicit 'Event' attribute).
/// </summary>
private static bool IsImplicitEventMethodCandidate(IMethodSymbol method, Compilation compilation)
{
// Only ordinary methods can be event methods. Property/event accessors, operators, constructors, destructors, etc.
// must be ignored even though some of them (like property setters) are non-static, non-virtual and void-returning.
// 'MethodKind.Ordinary' already excludes constructors and destructors.
if (method.MethodKind != MethodKind.Ordinary || method.IsStatic || method.IsVirtual || !method.ReturnsVoid)
{
return false;
}

// Methods marked with [NonEvent] are explicitly excluded from being events.
return !method.GetAttributes().Any(a => a.AttributeClass?.IsClrType(compilation, typeof(NonEventAttribute)) == true);
}

private static IOperation GetExpectedEventId(IInvocationOperation invocation)
{
return null!;
Expand Down
Loading