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 @@ -136,6 +136,7 @@ jobs:
frontend/roslyn/samples/DiCaptiveSample.cs \
frontend/roslyn/samples/SampleTypes.cs \
frontend/roslyn/samples/PipeFieldsSample.cs \
frontend/roslyn/samples/AppLifetimeSample.cs \
-o "$RUNNER_TEMP/facts.json"
cat "$RUNNER_TEMP/facts.json"
- name: Check facts through the core
Expand Down Expand Up @@ -283,6 +284,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 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
# escape (OWN014) must NOT fire — for both the name-based (`partial class
# App`) and base-based (`: Application`) shapes.
if echo "$out" | grep -q "AppLifetimeSample.cs"; then
echo "FAIL: a process-lived App static-event subscription was wrongly reported (OWN014 FP)"; exit 1
fi
# P-006 DI001 (captive dependency): the registration + constructor graph
# extracted from DiCaptiveSample.cs feeds ownlang/di.py. A singleton that
# captures a scoped service — directly, transitively through a transient,
Expand Down
40 changes: 40 additions & 0 deletions frontend/roslyn/OwnSharp.Extractor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,37 @@
IsHandler(right)
&& model.GetSymbolInfo(right).Symbol 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
// the process exits. Subscribing it to a process-lived static event
// (`AppDomain.CurrentDomain.UnhandledException`, `SystemEvents.*`) promotes nothing:
// its "leaked" lifetime already equals the process. So a static-source region escape
// (OWN014) raised from inside `App` is a false positive (found mining ScreenToGif
// and its bundled Translator tool, both flagged on the textbook unhandled-exception
// hook). Detected syntactically because WPF does not resolve on the Linux runner:
// either the class derives from `Application` / `System.Windows.Application`, or it
// is the conventional XAML-split `partial class App` (whose `: Application` lives in
// the generated `App.g.cs` partial the extractor never sees). Only the STATIC-source
// escape is suppressed; an instance-field subscription leak inside `App` still fires.
static bool IsProcessLivedApplication(TypeDeclarationSyntax cls)
{
if (cls.BaseList is { } bl)
foreach (var bt in bl.Types)
{
var n = bt.Type switch
{
IdentifierNameSyntax id => id.Identifier.Text,
QualifiedNameSyntax q => q.Right.Identifier.Text,
AliasQualifiedNameSyntax aq => aq.Name.Identifier.Text,
_ => null,
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (n is "Application")
return true;
}
return cls.Identifier.Text == "App"
&& cls.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
}

// P-004 severity tiering: of the subscriptions that survive the self-owned and
// static-handler exemptions (and are not timers), how long-lived is the event
// SOURCE? A static event lives for the whole process, so an undetached handler is
Expand Down Expand Up @@ -1808,7 +1839,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 1842 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 1842 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 1842 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 1842 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 1842 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 1842 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 Expand Up @@ -1906,6 +1937,10 @@
&& IsTemplatePartFetch(a.Right))
selfOwned.Add(tf.Name);

// Is this class the process-lived WPF application object? Used to drop the
// static-source region escape (OWN014) — `App` cannot be over-promoted.
var clsIsApp = IsProcessLivedApplication(cls);

var subs = new List<object>();
foreach (var a in assigns)
{
Expand Down Expand Up @@ -1938,6 +1973,11 @@
: SubscriptionSourceKind(a.Left, ev, model);
if (source == "local")
continue;
// Process-lived subscriber (the WPF `App` singleton): a static-source
// subscription promotes nothing — `App` already lives for the whole
// process — so the region escape (OWN014) is a false positive.
if (source == "static" && clsIsApp)
continue;
var released = unsub.Contains($"{a.Left}|{a.Right}")
|| (isTimer && Receiver(a.Left) is { } recv && stopped.Contains(recv));
subs.Add(new
Expand Down
46 changes: 46 additions & 0 deletions frontend/roslyn/samples/AppLifetimeSample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System;
using System.Windows;

namespace Own.Samples.WpfApp;

// P-004 process-lived-subscriber exemption (mined from ScreenToGif + its Translator).
//
// The WPF application object (`App`) is a process-lived singleton: subscribing it to
// a process-lived static event (AppDomain.CurrentDomain.UnhandledException) is the
// textbook global-exception hook and promotes nothing — App already lives for the
// whole process. So the static-source region escape (OWN014) must NOT fire on it.
// Two detection shapes, both must stay SILENT:

// (1) name-based: ScreenToGif's real shape — `partial class App` whose `: Application`
// lives in the generated `App.g.cs` partial the extractor never sees (here the
// only visible base is IDisposable).
public partial class App : IDisposable
{
private void App_Startup(object sender, EventArgs e)
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
}

private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Console.WriteLine(e.ExceptionObject);
}

public void Dispose() { }
}

// (2) base-based: a custom-named application object deriving from `Application`
// directly in the .cs (the base stays unresolved on the Linux runner, but the
// syntactic base-name detection still applies).
public class BootstrapApp : Application
{
public void Init()
{
AppDomain.CurrentDomain.UnhandledException += OnUnhandled;
}

private void OnUnhandled(object sender, UnhandledExceptionEventArgs e)
{
Console.WriteLine(e.ExceptionObject);
}
}
Loading