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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ jobs:
frontend/roslyn/samples/WhenAnyValueViewModel.cs \
frontend/roslyn/samples/DiCaptiveSample.cs \
frontend/roslyn/samples/SampleTypes.cs \
frontend/roslyn/samples/PipeFieldsSample.cs \
-o "$RUNNER_TEMP/facts.json"
cat "$RUNNER_TEMP/facts.json"
- name: Check facts through the core
Expand All @@ -154,6 +155,13 @@ jobs:
if echo "$out" | grep -q "OrdersViewModel.cs"; then
echo "FAIL: disposed subscription wrongly reported"; exit 1
fi
# Mined FP regression (Pipelines.Sockets.Unofficial): System.IO.Pipelines PipeReader/PipeWriter
# END WITH Reader/Writer but are NOT IDisposable (they finish via Complete(), not Dispose()), so
# an undisposed PipeReader/PipeWriter FIELD must NOT be flagged as a leak —
# IsNonDisposableReaderWriter excludes them from the field-disposable name heuristic.
if echo "$out" | grep -q "PipeFieldsSample.cs"; then
echo "FAIL: PipeReader/PipeWriter field wrongly reported as an undisposed-disposable leak"; exit 1
fi
# a lambda handler has no stored delegate, so it can NEVER be `-=`'d — the
# finding says so. (Same injected source as Customer -> also a warning.)
echo "$out" | grep -qE "LambdaHandlerViewModel\.cs:[0-9]+: warning: \[OWN001\]" \
Expand Down
15 changes: 13 additions & 2 deletions frontend/roslyn/OwnSharp.Extractor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1412,8 +1412,19 @@
static bool IsDisposableType(string t) =>
t is "IDisposable" or "IAsyncDisposable" or "CancellationTokenSource"
or "HttpClient" or "SerialPort" or "SqlConnection"
|| t.EndsWith("Stream") || t.EndsWith("Reader") || t.EndsWith("Writer")
|| t.EndsWith("Subscription");
|| ((t.EndsWith("Stream") || t.EndsWith("Reader") || t.EndsWith("Writer")
|| t.EndsWith("Subscription"))
&& !IsNonDisposableReaderWriter(t));

// BCL `…Reader`/`…Writer` types that the EndsWith name heuristic above matches but that are NOT
// IDisposable: System.IO.Pipelines `PipeReader`/`PipeWriter` finish via `Complete()`, not `Dispose()`.
// Excluding them stops the field-disposable detector flagging an undisposed PipeReader/PipeWriter
// field as a leak — a FALSE POSITIVE mined on Pipelines.Sockets.Unofficial (SocketConnection's
// `_input`/`_output`). Matched on the exact bare or `System.IO.Pipelines`-qualified spelling — NOT
// any simple-name match, so a project's own disposable `MyLib.PipeReader` is still flagged (Codex).
static bool IsNonDisposableReaderWriter(string t) =>
t is "PipeReader" or "PipeWriter"
or "System.IO.Pipelines.PipeReader" or "System.IO.Pipelines.PipeWriter";

// --- P-006: DI registration + constructor graph (DI001 captive dependency) ---
// A syntactic pass over the same trees, independent of the event/disposable
Expand Down Expand Up @@ -1782,7 +1793,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 1796 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 1796 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 1796 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 1796 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 1796 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 1796 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.
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
47 changes: 47 additions & 0 deletions frontend/roslyn/samples/PipeFieldsSample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;

// Mined false-positive regression guard (Pipelines.Sockets.Unofficial). `System.IO.Pipelines`
// PipeReader/PipeWriter END WITH "Reader"/"Writer", so the field-disposable NAME heuristic used to
// classify them as IDisposable and flag an undisposed PipeReader/PipeWriter FIELD as a leak. But they
// are NOT IDisposable — they finish via Complete(), not Dispose(). A class that constructs such fields
// and never disposes them must produce NO finding (mirrors SocketConnection._input/_output, which the
// checker wrongly reported before IsNonDisposableReaderWriter excluded these types).
public sealed class PipeHolder
{
private readonly PipeReader _input;
private readonly PipeWriter _output;

public PipeHolder()
{
_input = new NullReader(); // constructed (a `new`) -> a disposable-field candidate before the fix
_output = new NullWriter();
}

// No Dispose: PipeReader/PipeWriter are completed by the consumer, not disposed -> not a leak.
}

// Minimal PipeReader/PipeWriter implementations so the sample is self-contained (the abstract BCL
// types resolve from the framework reference set). PipeReader/PipeWriter expose Complete(), not
// Dispose() — they are not IDisposable.
internal sealed class NullReader : PipeReader
{
public override void AdvanceTo(SequencePosition consumed) => throw new NotImplementedException();
public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) => throw new NotImplementedException();
public override void CancelPendingRead() => throw new NotImplementedException();
public override void Complete(Exception exception = null) => throw new NotImplementedException();
public override ValueTask<ReadResult> ReadAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException();
public override bool TryRead(out ReadResult result) => throw new NotImplementedException();
}

internal sealed class NullWriter : PipeWriter
{
public override void Advance(int bytes) => throw new NotImplementedException();
public override void CancelPendingFlush() => throw new NotImplementedException();
public override void Complete(Exception exception = null) => throw new NotImplementedException();
public override ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException();
public override Memory<byte> GetMemory(int sizeHint = 0) => throw new NotImplementedException();
public override Span<byte> GetSpan(int sizeHint = 0) => throw new NotImplementedException();
}
Loading