@@ -211,6 +211,7 @@ jobs:
211211 frontend/roslyn/samples/SelfDetachingHandlerSample.cs \
212212 frontend/roslyn/samples/UsingFieldAcquisitionSample.cs \
213213 frontend/roslyn/samples/TemplatePartLocalCaptureSample.cs \
214+ frontend/roslyn/samples/EmptyDisposeSample.cs \
214215 frontend/roslyn/samples/AppScopedSourceSample.cs \
215216 -o "$RUNNER_TEMP/facts.json"
216217 cat "$RUNNER_TEMP/facts.json"
@@ -836,6 +837,19 @@ jobs:
836837 # ...while the same-named local in the OTHER method must still warn.
837838 echo "$out" | grep -qE "TemplatePartLocalCaptureSample\.cs:[0-9]+: warning: \[OWN001\].*'SameNameDifferentScopeSubscriber'" \
838839 || { echo "FAIL: expected OWN001 on the same-named-but-unrelated local in a different method"; exit 1; }
840+ # issue #225 — a user-defined type whose Dispose() body is provably EMPTY holds no resource,
841+ # so a LOCAL of it never disposed is not a leak (extends the named-BCL no-op exemption to any
842+ # type). Here the flat (non-flow) name path: a `*Reader`-named empty-Dispose local (ScratchReader)
843+ # is silent, while a non-empty `*Reader` local (LeakyReader) still leaks. (The semantic enumerator
844+ # form is exercised in the --flow-locals step below.)
845+ if echo "$out" | grep -q "'ScratchReader'"; then
846+ echo "FAIL: a local of a user type with a provably-empty Dispose() body was wrongly flagged (#225)"; exit 1
847+ fi
848+ echo "$out" | grep -qE "EmptyDisposeSample\.cs:[0-9]+:.*\[OWN001\].*'lr'.*'LeakyReader'" \
849+ || { echo "FAIL: a non-empty-Dispose local (LeakyReader) must still leak (#225 stays scoped)"; exit 1; }
850+ # Codex P2: an empty SYNC Dispose but a real DisposeAsync (a `*Reader` here) must still leak.
851+ echo "$out" | grep -qE "EmptyDisposeSample\.cs:[0-9]+:.*\[OWN001\].*'ar'.*'AsyncReader'" \
852+ || { echo "FAIL: a type with a real DisposeAsync (empty sync Dispose) must still leak (#225)"; exit 1; }
839853 # issue #228 — an Application-derived subscriber on a CURATED app-scoped
840854 # resolver result (PaletteHelper.GetThemeManager) with a method-group handler
841855 # of the App class itself must be SILENT: the source is process-lived (bound
@@ -864,7 +878,7 @@ jobs:
864878 # curated initializer — the stale declaration binding must not exempt.
865879 echo "$out" | grep -qE "AppScopedSourceSample\.cs:[0-9]+: warning: \[OWN001\].*'ReassignApp'" \
866880 || { echo "FAIL: expected OWN001 when the resolver-bound local is reassigned before the +="; exit 1; }
867- echo "OK: real C# -> facts -> OWN001 (subscription + timer + field + Subscribe + pool + local) + OWN014 (static-event region escape) + DI001 (captive dependency) + DI002 (scoped captured weakly) + DI003 (transient IDisposable captured by a singleton) + DI004 (transient IDisposable service-located from the root provider) + DI005 (scoped service cached from a created scope) + [OwnIgnore] suppression (silent-but-counted, SARIF suppressions) + #218 DP old->new subscription rotation (silent; controls flagged) + #228 curated app-scoped source in App (silent; controls flagged) at the C# location"
881+ echo "OK: real C# -> facts -> OWN001 (subscription + timer + field + Subscribe + pool + local) + OWN014 (static-event region escape) + DI001 (captive dependency) + DI002 (scoped captured weakly) + DI003 (transient IDisposable captured by a singleton) + DI004 (transient IDisposable service-located from the root provider) + DI005 (scoped service cached from a created scope) + [OwnIgnore] suppression (silent-but-counted, SARIF suppressions) + #218 DP old->new subscription rotation (silent; controls flagged) + #225 empty-Dispose local exemption (silent; controls flagged) + # 228 curated app-scoped source in App (silent; controls flagged) at the C# location"
868882 - name : Flow-sensitive local IDisposables (--flow-locals, P-016 B0b/B2)
869883 run : |
870884 # Path-sensitive flow analysis of local IDisposables — bugs the flat D1
@@ -873,7 +887,8 @@ jobs:
873887 frontend/roslyn/samples/FlowLocalsSample.cs \
874888 frontend/roslyn/samples/MemoryOwnerEscapeSample.cs \
875889 frontend/roslyn/samples/FactoryLeakSample.cs \
876- frontend/roslyn/samples/OverloadSigSample.cs --flow-locals -o "$RUNNER_TEMP/flow.json"
890+ frontend/roslyn/samples/OverloadSigSample.cs \
891+ frontend/roslyn/samples/EmptyDisposeSample.cs --flow-locals -o "$RUNNER_TEMP/flow.json"
877892 out=$(python -m ownlang ownir "$RUNNER_TEMP/flow.json" || true)
878893 echo "$out"
879894 echo "$out" | grep -q "OWN002" || { echo "FAIL: expected OWN002 (use-after-dispose)"; exit 1; }
@@ -1095,7 +1110,26 @@ jobs:
10951110 for ok in opened probe; do
10961111 if echo "$out" | grep -q "'$ok'"; then echo "FAIL: stage-2 silent case '$ok' was reported"; exit 1; fi
10971112 done
1098- 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)"
1113+ # issue #225 (semantic flow path — the real ClosedXML Slice.Enumerator shape): a LOCAL of a
1114+ # user type whose Dispose() body is provably EMPTY holds no resource, so never disposing it
1115+ # cannot leak. The three controls STILL leak: a real IDisposable ('r'), a non-empty *Reader
1116+ # ('lr'), and an empty override over a base that owns a real Dispose ('d').
1117+ echo "$out" | grep -qE "EmptyDisposeSample\.cs:[0-9]+:.*\[OWN001\].*'r' is never disposed" \
1118+ || { echo "FAIL: #225 flow: a real IDisposable local must still leak"; exit 1; }
1119+ echo "$out" | grep -qE "EmptyDisposeSample\.cs:[0-9]+:.*\[OWN001\].*'lr' is never disposed" \
1120+ || { echo "FAIL: #225 flow: a non-empty *Reader local must still leak"; exit 1; }
1121+ echo "$out" | grep -qE "EmptyDisposeSample\.cs:[0-9]+:.*\[OWN001\].*'d' is never disposed" \
1122+ || { echo "FAIL: #225 flow: an empty override over a real base Dispose must still leak"; exit 1; }
1123+ # Codex P2: an empty SYNC Dispose() but a real DisposeAsync() (IDisposable + IAsyncDisposable)
1124+ # must NOT be exempted — the real cleanup is async and the flow detector treats it as a release.
1125+ echo "$out" | grep -qE "EmptyDisposeSample\.cs:[0-9]+:.*\[OWN001\].*'ar' is never disposed" \
1126+ || { echo "FAIL: #225 flow: a type with a real DisposeAsync (empty sync Dispose) must still leak"; exit 1; }
1127+ # exactly 4 EmptyDisposeSample findings -> the two provably-empty-Dispose locals (the enumerator
1128+ # and ScratchReader) are SILENT; any leak of them would push the count past 4.
1129+ n225=$(echo "$out" | grep -cE "EmptyDisposeSample\.cs:[0-9]+:.*\[OWN00")
1130+ [ "$n225" = "4" ] \
1131+ || { echo "FAIL: #225 flow: expected exactly 4 EmptyDisposeSample findings (controls only), got $n225"; exit 1; }
1132+ 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, #225 empty-Dispose local exempt, beyond flat)"
10991133 - name : Gallery C#-native bad/ok pairs (examples/gallery/cs/)
11001134 run : |
11011135 # C#-native mirror of examples/gallery/*.own, run through the real extractor ->
0 commit comments