-
Notifications
You must be signed in to change notification settings - Fork 0
feat: PoC oficially proven? #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5d8ca9b
336281e
e306d9e
e9fcc49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| using System; | ||
| using System.IO; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| // P-016 B0b/B2 (experimental, --flow-locals): path-sensitive flow analysis of | ||
| // local IDisposables. These are bugs the flat D1 detector cannot catch (it only | ||
| // asks "disposed anywhere?"). Distinct local names let CI assert each verdict. | ||
| public class FlowLocalsSample | ||
| { | ||
| // OWN002: used after Dispose() | ||
| public void UseAfterDispose() | ||
| { | ||
| var uad = new MemoryStream(); | ||
|
Check failure on line 14 in frontend/roslyn/samples/FlowLocalsSample.cs
|
||
| uad.WriteByte(1); | ||
| uad.Dispose(); | ||
| uad.WriteByte(2); | ||
| } | ||
|
|
||
| // OWN001: disposed only on the `then` path -> leaks on the else path | ||
| public void LeakOnElse(bool c) | ||
| { | ||
| var leak = new MemoryStream(); | ||
|
Check failure on line 23 in frontend/roslyn/samples/FlowLocalsSample.cs
|
||
| if (c) | ||
| { | ||
| leak.Dispose(); | ||
| } | ||
| } | ||
|
|
||
| // OWN003: disposed twice | ||
| public void DoubleDispose() | ||
| { | ||
| var dbl = new MemoryStream(); | ||
|
Check failure on line 33 in frontend/roslyn/samples/FlowLocalsSample.cs
|
||
| dbl.Dispose(); | ||
| dbl.Dispose(); | ||
| } | ||
|
|
||
| // clean: disposed on all paths -> silent | ||
| public void Clean() | ||
| { | ||
| var clean = new MemoryStream(); | ||
| clean.WriteByte(1); | ||
| clean.Dispose(); | ||
| } | ||
|
|
||
| // has a loop -> method honestly skipped (no flow finding) | ||
| public void HasLoop() | ||
| { | ||
| var looped = new MemoryStream(); | ||
| for (int i = 0; i < 3; i++) { looped.WriteByte((byte)i); } | ||
| looped.Dispose(); | ||
| } | ||
|
|
||
| // escapes (returned) -> not tracked | ||
| public Stream Escapes() | ||
| { | ||
| var esc = new MemoryStream(); | ||
| return esc; | ||
| } | ||
|
|
||
| // dispose-optional: Task is IDisposable but disposing it is unnecessary | ||
| // (CA2000-exempt) -> silent, not a leak. | ||
| public void TaskIsExempt() | ||
| { | ||
| var exemptTask = new Task(() => { }); | ||
| exemptTask.Start(); | ||
| } | ||
|
|
||
| // real leak: System.Threading.Timer owns an unmanaged timer-queue handle and | ||
| // MUST be disposed (not dispose-optional) -> OWN001. The flat detector misses | ||
| // this (Timer is absent from its curated allowlist); the semantic flow path | ||
| // catches it. | ||
| public void TimerLeaks() | ||
| { | ||
| var realTimer = new Timer(_ => { }); | ||
|
Check failure on line 75 in frontend/roslyn/samples/FlowLocalsSample.cs
|
||
| realTimer.Change(0, 1000); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flow lowering currently misses valid disposal paths and can misreport leaks.
Line 226 lowers
usingbodies but does not emit a release forusing(existingLocal).Lines 243-246 only match direct
Dispose/Closeinvocations and missDisposeAsync(includingawait x.DisposeAsync()). With--flow-localsenabled (and D1 suppressed), these become false OWN001/OWN009-style outcomes.Proposed fix
@@ case UsingStatementSyntax us: // using(...) {body}: the using local is auto-disposed (untracked); still // lower the body so a tracked plain local used inside is seen. - return us.Statement is null || LowerFlowStmt(us.Statement, tracked, nodes); + if (us.Statement is not null && !LowerFlowStmt(us.Statement, tracked, nodes)) + return false; + // using(existingTrackedLocal) disposes that local at scope end. + if (us.Expression is IdentifierNameSyntax uid + && tracked.Contains(uid.Identifier.Text)) + nodes.Add(new { op = "release", var = uid.Identifier.Text, line = LineOf(us) }); + return true; @@ static void EmitFlowExpr(ExpressionSyntax expr, HashSet<string> tracked, List<object> nodes) { // x.Dispose()/x.Close() on a tracked local -> release. - if (expr is InvocationExpressionSyntax inv + var call = expr switch + { + InvocationExpressionSyntax i => i, + AwaitExpressionSyntax { Expression: InvocationExpressionSyntax i } => i, + _ => null, + }; + if (call is InvocationExpressionSyntax inv && inv.Expression is MemberAccessExpressionSyntax ma - && ma.Name.Identifier.Text is "Dispose" or "Close" + && ma.Name.Identifier.Text is "Dispose" or "Close" or "DisposeAsync" && ma.Expression is IdentifierNameSyntax rid && tracked.Contains(rid.Identifier.Text)) { nodes.Add(new { op = "release", var = rid.Identifier.Text, line = LineOf(inv) }); return; }Also applies to: 243-246
🤖 Prompt for AI Agents