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
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ jobs:
frontend/roslyn/samples/InjectedDcViewSample.xaml.cs \
frontend/roslyn/samples/ResolvedDisposableSample.cs \
frontend/roslyn/samples/FieldReleaseSample.cs \
frontend/roslyn/samples/StaticClassEscapeSample.cs \
-o "$RUNNER_TEMP/facts.json"
cat "$RUNNER_TEMP/facts.json"
- name: Check facts through the core
Expand Down Expand Up @@ -321,6 +322,14 @@ jobs:
if echo "$out" | grep -q "CleanStaticEventViewModel"; then
echo "FAIL: an unsubscribed (released) static-event subscription was wrongly reported"; exit 1
fi
# P-004 robust static-handler exemption (mined: ImageSharp MemoryAllocatorValidator): a
# static-METHOD handler on a static event stores a null-target delegate -> no instance is
# retained -> OWN014 must NOT fire, even when the method-group symbol surfaces as a member
# group (now resolved via CandidateSymbols). (StaticEventEscapeViewModel above proves an
# INSTANCE handler on the same static event still escapes, so this stays scoped.)
if echo "$out" | grep -q "StaticAllocationCounter"; then
echo "FAIL: a static-method handler on a static event was wrongly reported as a region escape"; exit 1
fi
# P-004 process-lived-subscriber exemption (mined: ScreenToGif App +
# Translator): the WPF `App` singleton hooking the process-lived
# AppDomain.UnhandledException promotes nothing, so the static-source region
Expand Down
21 changes: 17 additions & 4 deletions frontend/roslyn/OwnSharp.Extractor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,23 @@
// Target is null, so no instance is retained — the subscription cannot leak a
// subscriber, however long-lived the source. Only method-group handlers
// (identifier / member access) are judged; lambdas and delegate-typed values may
// capture state and are left as leak candidates.
static bool IsStaticHandler(ExpressionSyntax right, SemanticModel model) =>
IsHandler(right)
&& model.GetSymbolInfo(right).Symbol is IMethodSymbol { IsStatic: true };
// capture state and are left as leak candidates. A method group's symbol can surface
// as a MEMBER GROUP (Symbol == null, CandidateSymbols populated) instead of the bound
// method, so fall back to the candidates — else a genuinely static-method handler is
// missed and a static-source subscription that retains NO instance is mis-reported as
// a region escape (mined: ImageSharp MemoryAllocatorValidator's static MemoryDiagnostics
// handlers). When falling back, require ALL candidates static so an overload set that
// mixes a static and an instance method is not wrongly exempted.
static bool IsStaticHandler(ExpressionSyntax right, SemanticModel model)
{
if (!IsHandler(right))
return false;
var info = model.GetSymbolInfo(right);
if (info.Symbol is { } s)
return s is IMethodSymbol { IsStatic: true };
var cands = info.CandidateSymbols;
return cands.Length > 0 && cands.All(c => c is IMethodSymbol { IsStatic: true });
}

// P-004 process-lived-subscriber exemption: the WPF application object (`App`) is a
// process-lived singleton — exactly one instance, created at startup, alive until
Expand Down Expand Up @@ -1931,7 +1944,7 @@
.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)
.Where(p => p.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
.ToList();
var refNames = new HashSet<string>(tpa.Select(Path.GetFileName), StringComparer.OrdinalIgnoreCase);

Check warning on line 1947 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / own-check SARIF -> GitHub code scanning (dog-food)

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 1947 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / own-check SARIF -> GitHub code scanning (dog-food)

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 1947 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / C# leak extractor (Roslyn) -> OwnIR -> core

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 1947 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / own-check repo scan (github + msbuild) + composite action

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 1947 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / C# leak extractor (Roslyn) -> OwnIR -> core

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 1947 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / own-check repo scan (github + msbuild) + composite action

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.
var references = tpa.Select(p => (MetadataReference)MetadataReference.CreateFromFile(p)).ToList();
// P-004 WPF profile: widen the reference set with assemblies named by the
// OWN_EXTRA_REF_DIRS env var (colon-separated dirs) — e.g. the WindowsDesktop ref
Expand Down
29 changes: 29 additions & 0 deletions frontend/roslyn/samples/StaticClassEscapeSample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;

namespace Own.Samples;

// P-004 static-handler exemption, robustly (mined: ImageSharp MemoryAllocatorValidator).
//
// A static class whose static ctor hooks a process-lived STATIC event with a STATIC METHOD
// handler stores a delegate whose Target is null — no instance is retained, so OWN014 must NOT
// fire. The mined case slipped through because the method-group symbol can surface as a member
// group (Symbol == null), which IsStaticHandler now resolves via CandidateSymbols. Contrast:
// StaticEventEscapeViewModel — an INSTANCE handler on a static event — must still raise OWN014
// (capturing lambdas in a static class likewise still escape: the closure is retained).

public static class StaticDiagnosticsBus
{
public static event EventHandler? Allocated;

public static void Raise() => Allocated?.Invoke(null, EventArgs.Empty);
}

public static class StaticAllocationCounter
{
static StaticAllocationCounter()
{
StaticDiagnosticsBus.Allocated += OnAllocated; // static-method handler -> null target -> SILENT
}

private static void OnAllocated(object? sender, EventArgs e) { }
}
Loading