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
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,10 @@ jobs:
# case disposes (no default) -> last case is the tail, no phantom no-match leak.
# `ncf`: `ncf?.Dispose()` (null-conditional) in a threaded finally IS a release
# (member-binding form), so it is disposed on the return path -> silent (Codex review).
for ok in clean looped esc exemptTask whileClean asyncDisposed asyncDisposedCfg tfClean tfCatch tfRet tfNull cda daci cif ctorLater lamPrior other doClean swAll ncf captured shaClean stopped defer; do
# `ctorMoved`: a pooled buffer handed to a constructor whose result is RETURNED transfers
# ownership to the returned wrapper -> escaped -> silent (mined FP on
# Pipelines.Sockets.Unofficial: ArrayPoolBufferWriter.CreateNewSegment).
for ok in clean looped esc exemptTask whileClean asyncDisposed asyncDisposedCfg tfClean tfCatch tfRet tfNull cda daci cif ctorLater lamPrior other doClean swAll ncf captured shaClean stopped defer ctorMoved; do
if echo "$out" | grep -q "'$ok'"; then echo "FAIL: silent/exempt case '$ok' was reported"; exit 1; fi
done
echo "OK: flow-sensitive OWN001/002/003 on real C# (path-sensitive, loops via while/foreach/for, try/finally sequential, never-vs-every-path wording, dispose-optional exempt, beyond flat)"
Expand Down
25 changes: 24 additions & 1 deletion frontend/roslyn/OwnSharp.Extractor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,21 @@
&& i.ArgumentList.Arguments[0].Expression is IdentifierNameSyntax buf
? buf.Identifier.Text : null;

// Is `idn` a pooled buffer passed as an argument to a CONSTRUCTOR whose result ESCAPES this method —
// `return new Wrapper(…, buf, …)` or `_field = new Wrapper(…, buf, …)`? Such a `new` hands the buffer
// to the constructed object, which becomes responsible for Return, so the buffer leaves the method
// inside the escaping object (an ownership transfer, not a borrow). One level only: the `new` must be
// the direct return value or assigned to a real FIELD — a `new` stored in a LOCAL stays a borrow (the
// local is method-scoped; a wrapper that never leaves the method and never Returns the buffer is a
// real leak, Codex), as does a `new` buried in another expression. The field check is symbol-based
// (`IFieldSymbol`) so an assignment to a same-named LOCAL is not mistaken for a field.
static bool PassedToEscapingCtor(IdentifierNameSyntax idn, SemanticModel model) =>
idn.Parent is ArgumentSyntax { Parent: ArgumentListSyntax
{ Parent: BaseObjectCreationExpressionSyntax oce } }
&& (oce.Parent is ReturnStatementSyntax
|| (oce.Parent is AssignmentExpressionSyntax a && a.Right == oce
&& model.GetSymbolInfo(a.Left).Symbol is IFieldSymbol));

// Is `t` the System.Buffers.MemoryPool<T> type — the Dispose-based pool. Mirrors IsArrayPoolType
// (checked on the resolved symbol, so an aliased/injected `MemoryPool<T>` receiver binds and a
// look-alike does not).
Expand Down Expand Up @@ -1782,7 +1797,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 1800 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 1800 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 1800 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 1800 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 1800 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 1800 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 @@ -2394,8 +2409,16 @@
if (!usingMemoryOwners.Contains(nm))
escapedLocals.Add(nm);
}
// A pooled buffer handed as an argument is normally a BORROW (the renter Returns it),
// NOT an escape — so `pool.Return(buf); Work(buf)` still trips use-after-return. But a
// pooled buffer passed to a CONSTRUCTOR whose result ESCAPES this method (`return new
// Wrapper(buf)` / `_field = new Wrapper(buf)`) transfers ownership to that object, which
// becomes responsible for Return — so the buffer is NOT leaked here. Treat that as an
// escape (mined FP on Pipelines.Sockets.Unofficial: ArrayPoolBufferWriter.CreateNewSegment
// returns `new ArrayPoolRefCountedSegment(pool, array, prev)`).
else if ((idn.Parent is AssignmentExpressionSyntax asg && asg.Right == idn)
|| (idn.Parent is ArgumentSyntax && !poolBuffers.Contains(nm) && !consumedArg))
|| (idn.Parent is ArgumentSyntax && !poolBuffers.Contains(nm) && !consumedArg)
|| (poolBuffers.Contains(nm) && PassedToEscapingCtor(idn, model)))
escapedLocals.Add(nm);
}
var tracked = new HashSet<string>(candidates);
Expand Down
22 changes: 22 additions & 0 deletions frontend/roslyn/samples/FlowLocalsSample.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Net;
Expand All @@ -15,7 +16,7 @@
// OWN002: used after Dispose()
public void UseAfterDispose()
{
var uad = new MemoryStream();

Check failure on line 19 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'uad' is used after it is disposed

Check failure on line 19 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN002

[OWN002] IDisposable local 'uad' is used after it is disposed [resource: disposable]

Check failure on line 19 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'uad' is used after it is disposed

Check failure on line 19 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN002

[OWN002] IDisposable local 'uad' is used after it is disposed [resource: disposable]
uad.WriteByte(1);
uad.Dispose();
uad.WriteByte(2);
Expand All @@ -24,7 +25,7 @@
// 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 28 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'leak' may not be disposed on every path (leak)

Check failure on line 28 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN001

[OWN001] IDisposable local 'leak' may not be disposed on every path (leak) [resource: disposable]

Check failure on line 28 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'leak' may not be disposed on every path (leak)

Check failure on line 28 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN001

[OWN001] IDisposable local 'leak' may not be disposed on every path (leak) [resource: disposable]
if (c)
{
leak.Dispose();
Expand All @@ -34,7 +35,7 @@
// OWN003: disposed twice
public void DoubleDispose()
{
var dbl = new MemoryStream();

Check failure on line 38 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'dbl' is disposed more than once

Check failure on line 38 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN003

[OWN003] IDisposable local 'dbl' is disposed more than once [resource: disposable]

Check failure on line 38 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'dbl' is disposed more than once

Check failure on line 38 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN003

[OWN003] IDisposable local 'dbl' is disposed more than once [resource: disposable]
dbl.Dispose();
dbl.Dispose();
}
Expand Down Expand Up @@ -64,7 +65,7 @@
{
while (n > 0)
{
var whileLeak = new MemoryStream();

Check failure on line 68 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'whileLeak' is never disposed (leak)

Check failure on line 68 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN001

[OWN001] IDisposable local 'whileLeak' is never disposed (leak) [resource: disposable]

Check failure on line 68 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'whileLeak' is never disposed (leak)

Check failure on line 68 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN001

[OWN001] IDisposable local 'whileLeak' is never disposed (leak) [resource: disposable]
whileLeak.WriteByte(1);
n = n - 1;
}
Expand All @@ -75,7 +76,7 @@
{
foreach (var it in items)
{
var foreachLeak = new MemoryStream();

Check failure on line 79 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'foreachLeak' is never disposed (leak)

Check failure on line 79 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN001

[OWN001] IDisposable local 'foreachLeak' is never disposed (leak) [resource: disposable]

Check failure on line 79 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'foreachLeak' is never disposed (leak)

Check failure on line 79 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN001

[OWN001] IDisposable local 'foreachLeak' is never disposed (leak) [resource: disposable]
foreachLeak.WriteByte((byte)it);
}
}
Expand All @@ -87,7 +88,7 @@
{
for (int i = 0; i < n; i++)
{
var forLeak = new MemoryStream();

Check failure on line 91 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'forLeak' is never disposed (leak)

Check failure on line 91 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN001

[OWN001] IDisposable local 'forLeak' is never disposed (leak) [resource: disposable]

Check failure on line 91 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

IDisposable local 'forLeak' is never disposed (leak)

Check failure on line 91 in frontend/roslyn/samples/FlowLocalsSample.cs

View workflow job for this annotation

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

OWN001

[OWN001] IDisposable local 'forLeak' is never disposed (leak) [resource: disposable]
forLeak.WriteByte((byte)i);
}
}
Expand Down Expand Up @@ -504,6 +505,27 @@
defer.WriteByte(1); // must NOT trip OWN002 (it would, without the fix)
defer.Dispose(); // disposed here -> balanced -> silent
}

// NOT a leak (mined FP on Pipelines.Sockets.Unofficial — ArrayPoolBufferWriter.CreateNewSegment):
// a pooled buffer handed to a constructor whose result is RETURNED transfers ownership to the
// returned wrapper (which Returns the buffer on its own teardown), so this method does not leak it
// even though it never calls Return -> silent ('ctorMoved'). A plain borrow `Work(buf)` still
// leaks if not returned, so this is specifically the escaping-constructor transfer.
public PooledHolder PooledIntoReturnedCtor(int n)
{
var ctorMoved = ArrayPool<byte>.Shared.Rent(n);
return new PooledHolder(ctorMoved);
}
}

// Takes ownership of a pooled buffer (Returns it on teardown) — the wrapper that
// PooledIntoReturnedCtor hands its rented buffer to. Models the ownership transfer through a
// constructor argument that the escape analysis must recognise.
internal sealed class PooledHolder
{
private readonly byte[] _buffer;
public PooledHolder(byte[] buffer) => _buffer = buffer;
public void Release() => ArrayPool<byte>.Shared.Return(_buffer);
}

// A domain exception type literally named `Exception`, in a non-System namespace — the
Expand Down
Loading