-
Notifications
You must be signed in to change notification settings - Fork 0
Claude/fp field release recognition #86
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
cec7c7e
dc1a1a7
8e80d66
0cc0d5b
eeafdb6
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,48 @@ | ||
| using System; | ||
| using System.Buffers; | ||
| using System.IO; | ||
|
|
||
| namespace Own.Samples; | ||
|
|
||
| // Field release recognition (mined: ImageSharp). Two shapes the field detectors missed: | ||
| // #2 null-conditional disposal `field?.Dispose()`, and | ||
| // #3 a pooled FIELD released in a DIFFERENT member than the rent (ctor rent + Dispose | ||
| // return), or transferred into a field-stored guard that owns/returns it. | ||
|
|
||
| // #2: an IDisposable field disposed via the null-conditional `?.Dispose()` -> SILENT. | ||
| public sealed class DisposesViaConditional : IDisposable | ||
| { | ||
| private readonly MemoryStream stream = new(); | ||
|
|
||
| public void Dispose() => this.stream?.Dispose(); // null-conditional -> now recognized | ||
| } | ||
|
|
||
| // #2 control: an IDisposable field the class new's but never disposes -> must WARN. | ||
| public sealed class NeverDisposesField | ||
| { | ||
| private readonly MemoryStream stream = new(); | ||
|
Check failure on line 23 in frontend/roslyn/samples/FieldReleaseSample.cs
|
||
|
|
||
|
|
||
| public long Use() => this.stream.Length; | ||
| } | ||
|
|
||
| // #3: a pooled buffer FIELD rented in the ctor and Returned in Dispose (cross-member) -> SILENT. | ||
| public sealed class PoolFieldReturnedInDispose : IDisposable | ||
| { | ||
| private readonly byte[] returnedBuf; | ||
|
|
||
| public PoolFieldReturnedInDispose(int n) => this.returnedBuf = ArrayPool<byte>.Shared.Rent(n); | ||
|
|
||
| public void Dispose() => ArrayPool<byte>.Shared.Return(this.returnedBuf); | ||
|
|
||
| public byte First() => this.returnedBuf[0]; | ||
| } | ||
|
|
||
| // #3 control: a pooled buffer FIELD rented but NEVER returned -> must WARN. | ||
| public sealed class PoolFieldLeaked | ||
| { | ||
| private readonly byte[] leakedBuf; | ||
|
|
||
| public PoolFieldLeaked(int n) => this.leakedBuf = ArrayPool<byte>.Shared.Rent(n); | ||
|
Check failure on line 45 in frontend/roslyn/samples/FieldReleaseSample.cs
|
||
|
|
||
|
|
||
| public byte First() => this.leakedBuf[0]; | ||
| } | ||
| 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) { } | ||
| } |
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.
When a field rented from
ArrayPool<T>is assigned in one member, this class-wide release set treats any invocation namedReturnwith that field as the first argument as a release, without verifying that the receiver is actuallySystem.Buffers.ArrayPool<T>. In a class that calls something likesomeCache.Return(this._buf)or another unrelatedReturn(_buf),fieldReleasedmarks_bufreleased and the later pool fact is suppressed, so a real rented-but-never-returned buffer is missed. Please reuse the semantic ArrayPool check used for rents/PoolReturnBufferbefore adding the field to this set.Useful? React with 👍 / 👎.