Skip to content

Commit 333262c

Browse files
committed
OX-02: keep the extractor's disk read sites where P-037 pins them; E3/E4 on a self-contained input
- Program.cs keeps 'if (!File.Exists(path))' and 'text = File.ReadAllText(path);' (tests/test_p037_evidence.py anchors and counts them); the overlay is consulted first through TryOverlay, which touches no filesystem. CLI behaviour unchanged. - check_extractor_in_process E3/E4 overlay frontend/roslyn/samples instead of OrderBackend, whose extraction refuses on a clean checkout (no bin/, EF types unresolved) and so compared no facts with no facts in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL
1 parent bdeb874 commit 333262c

4 files changed

Lines changed: 43 additions & 35 deletions

File tree

‎frontend/roslyn/OwnSharp.Extractor/InProcess.cs‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ partial class Program
2222
internal static StringComparer PathComparer =>
2323
OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
2424

25-
static bool SourceExists(string path) =>
26-
SourceOverlay?.ContainsKey(Path.GetFullPath(path)) == true || File.Exists(path);
27-
28-
static string ReadSource(string path) =>
29-
SourceOverlay is not null && SourceOverlay.TryGetValue(Path.GetFullPath(path), out var text)
30-
? text
31-
: File.ReadAllText(path);
25+
/// <summary>The overlaid text of <paramref name="path"/>, when an overlay holds it. No
26+
/// filesystem access: the disk read stays at its one site in the parse loop.</summary>
27+
static bool TryOverlay(string path, out string text)
28+
{
29+
text = "";
30+
if (SourceOverlay is null || !SourceOverlay.TryGetValue(Path.GetFullPath(path), out var held))
31+
return false;
32+
text = held;
33+
return true;
34+
}
3235
}
3336

3437
namespace OwnSharp.Extractor

‎frontend/roslyn/OwnSharp.Extractor/Program.cs‎

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5703,23 +5703,26 @@ static bool IsPublicCtor(SyntaxTokenList modifiers)
57035703
// Defensive: an explicit input that is not a readable file (a directory
57045704
// passed by mistake, a deleted path) is skipped with a note, never an
57055705
// unhandled exception that aborts the whole scan.
5706-
if (!SourceExists(path))
5706+
// OX-02: an IDE's unsaved buffer for this path is read INSTEAD of the file
5707+
// (InProcessExtractor). The CLI never has an overlay, so for it this is the disk path
5708+
// below, unchanged.
5709+
if (!TryOverlay(path, out var text))
57075710
{
5708-
Console.Error.WriteLine($"ownsharp-extract: skipping (not a file): {path}");
5709-
continue;
5710-
}
5711-
string text;
5712-
try
5713-
{
5714-
// OX-02: an IDE's unsaved buffer for this path wins over the file on disk
5715-
// (InProcessExtractor); with no overlay — the CLI — this is File.ReadAllText.
5716-
text = ReadSource(path);
5717-
}
5718-
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
5719-
{
5720-
// A locked/unreadable file is skipped with a note, not an abort.
5721-
Console.Error.WriteLine($"ownsharp-extract: skipping unreadable file: {path} ({ex.Message})");
5722-
continue;
5711+
if (!File.Exists(path))
5712+
{
5713+
Console.Error.WriteLine($"ownsharp-extract: skipping (not a file): {path}");
5714+
continue;
5715+
}
5716+
try
5717+
{
5718+
text = File.ReadAllText(path);
5719+
}
5720+
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
5721+
{
5722+
// A locked/unreadable file is skipped with a note, not an abort.
5723+
Console.Error.WriteLine($"ownsharp-extract: skipping unreadable file: {path} ({ex.Message})");
5724+
continue;
5725+
}
57235726
}
57245727
parsed.Add((Rel(path), CSharpSyntaxTree.ParseText(text, path: path)));
57255728
if (path.EndsWith(".xaml.cs", StringComparison.OrdinalIgnoreCase))

‎scripts/owen_live_mutations.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,8 @@
7272
(
7373
"M6",
7474
os.path.join(EXTRACTOR, "InProcess.cs"),
75-
"SourceOverlay is not null && "
76-
"SourceOverlay.TryGetValue(Path.GetFullPath(path), out var text)",
77-
"false && SourceOverlay!.TryGetValue(Path.GetFullPath(path), out var text)",
75+
"if (SourceOverlay is null || !SourceOverlay.TryGetValue(",
76+
"if (true || !SourceOverlay!.TryGetValue(",
7877
"gate",
7978
["K2-unsaved-own002"],
8079
),

‎tests/check_extractor_in_process.py‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,23 +143,25 @@ def main() -> int:
143143
elif _read(outs[i]) != _read(cli_out[i]):
144144
fails.append(f"{label} {name}: facts differ from the command line's")
145145

146-
# E3/E4: OrderBackend, the endpoint file overlaid
147-
project = "samples/OrderBackend/OrderBackend"
148-
target = os.path.join(ROOT, project, "OrderEndpoints.cs")
146+
# E3/E4: the samples directory, one file overlaid. Self-contained on purpose: a run
147+
# whose facts depend on a built bin/ (OrderBackend's EF types) would refuse on a
148+
# clean checkout and compare no facts with no facts.
149+
project = SAMPLES
150+
target = os.path.join(ROOT, project, "FlowLocalsSample.cs")
149151
with open(target, encoding="utf-8") as f:
150152
original = f.read()
151153
same = os.path.join(tmp, "same.cs")
152154
with open(same, "w", encoding="utf-8", newline="") as f:
153155
f.write(original)
154156
# a non-escaping disposable local: the flow pass emits a function for it
155157
changed_text = original + (
156-
"\nnamespace OrderBackend { static class OverlayProbe { static void M() "
157-
"{ var s = new System.IO.MemoryStream(); s.WriteByte(1); } } }\n"
158+
"\nstatic class OverlayProbe { static void M() "
159+
"{ var s = new System.IO.MemoryStream(); s.WriteByte(1); } }\n"
158160
)
159161
changed = os.path.join(tmp, "changed.cs")
160162
with open(changed, "w", encoding="utf-8", newline="") as f:
161163
f.write(changed_text)
162-
args = [f"{project}/OrderBackend.csproj", "--flow-locals"]
164+
args = [project, "--flow-locals"]
163165
base_out = os.path.join(tmp, "e3-base.json")
164166
_cli(dll, args, base_out)
165167
e3 = os.path.join(tmp, "e3.json")
@@ -177,17 +179,18 @@ def main() -> int:
177179
fails.append("E3: an overlay equal to the disk changed the facts")
178180
# the same change made on disk, in a copy of the tree, run by the command line
179181
copy = os.path.join(tmp, "tree")
180-
# bin/ included: a .csproj input auto-references the project's built output
181182
shutil.copytree(os.path.join(ROOT, project), os.path.join(copy, project))
182183
with open(
183-
os.path.join(copy, project, "OrderEndpoints.cs"), "w", encoding="utf-8", newline=""
184+
os.path.join(copy, project, "FlowLocalsSample.cs"), "w", encoding="utf-8", newline=""
184185
) as f:
185186
f.write(changed_text)
186187
disk_out = os.path.join(tmp, "e4-disk.json")
187188
_cli(dll, args, disk_out, cwd=copy)
188189
checks += 1
189190
e4_bytes = _read(e4)
190-
if e4_bytes is None or e4_bytes == _read(base_out):
191+
if _read(base_out) is None:
192+
fails.append("E4: the unchanged samples produced no facts; nothing was compared")
193+
elif e4_bytes is None or e4_bytes == _read(base_out):
191194
fails.append(
192195
"E4: an overlay that differs from the disk did not change the facts "
193196
"(the disk was read instead of the buffer)"

0 commit comments

Comments
 (0)