em 02: data-flow classification (params/returns/locals buckets)

- Tooling/DataFlowClassifier: buckets a resolved selection via Roslyn
  DataFlowAnalysis. Selection flow + tail flow (statements AFTER the
  selection in the same block) both use the two-argument AnalyzeDataFlow
  overload on the contiguous run; the em 01 spike verdict is pinned in
  tests — a synthetic BlockSyntax (parent decision #3) throws
  ArgumentException "statements not within tree" and would re-bind
  symbols, breaking the written-inside ∩ read-after identity match.
- Buckets per parent spec as plain-string records: in-params = local +
  parameter reads (filtering the implicit `this`; fields/properties stay
  in the em 03 extract-first bucket), ByRef = reassigned inside via a
  non-declaration write (declaration initializers are not write-backs),
  returns = written ∧ read-after (tail, branch-insensitive
  over-approximation) + trailing `return X;` simple-name candidate,
  locals = written ∧ never read-after.
- CLI now prints the raw bucket dump after the statement count line;
  classification failures exit 1 with a clean message (exit 0/1/2
  contract preserved).
- Tests: 6 new (in-param incl. `this` non-leak, multi-statement
  two-argument path, trailing-return, ref-vs-in differential, extract-first
  non-leak, synthetic-block dead path, single-statement one-argument path);
  shared DemoFixture loader; fixed pre-existing CS8602 in DemoFixtureTests.
  36/36 green.
This commit is contained in:
2026-09-12 15:52:16 +01:00
parent b012d0aaa0
commit 7f0981e012
5 changed files with 542 additions and 11 deletions
+44 -1
View File
@@ -1,4 +1,5 @@
using ExtractMethod.Tooling;
using Microsoft.CodeAnalysis;
// CLIs are boring on purpose: argument parsing and printing live here, all
// Roslyn logic lives in Tooling/ so the tests can drive it directly.
@@ -31,7 +32,7 @@ var tree = CompilationLoader.ParseFile(file);
// 2. build the scratch compilation (refs from TRUSTED_PLATFORM_ASSEMBLIES)
var compilation = CompilationLoader.CreateCompilation(tree, Path.GetFileNameWithoutExtension(file));
if (compilation.GetDiagnostics().Any(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error))
if (compilation.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error))
{
Console.Error.WriteLine("warning: the file does not compile cleanly under a plain Roslyn compilation; reporting syntax-level resolution only");
}
@@ -48,4 +49,46 @@ Console.WriteLine(
$"{report.Count} statement(s) selected, lines {report.StartLine}..{report.EndLine} " +
$"in {report.Method?.Identifier.ValueText}(): " +
string.Join(", ", report.Kinds));
// 4. classify the data flow into the raw buckets (em 02: no pretty report yet)
// A classification failure is a semantic resolution error: same exit code as
// the resolver, message on stderr, but never a stack trace.
ExtractionSuggestion suggestion;
try
{
suggestion = DataFlowClassifier.Classify(compilation.GetSemanticModel(tree), report);
}
catch (Exception e) when (e is InvalidOperationException or ArgumentException)
{
Console.Error.WriteLine($"error: data-flow analysis failed: {e.Message}");
return SelectionResolver.ExitError;
}
foreach (var param in suggestion.Params)
{
Console.WriteLine($"params: {param.Name} ({param.Type}){(param.ByRef ? " [ref]" : " [in]")}");
}
if (suggestion.Params.Count == 0)
{
Console.WriteLine("params: (none)");
}
foreach (var name in suggestion.Returns)
{
Console.WriteLine($"returns: {name}");
}
if (suggestion.Returns.Count == 0)
{
Console.WriteLine("returns: (none)");
}
foreach (var name in suggestion.Locals)
{
Console.WriteLine($"locals: {name}");
}
if (suggestion.Locals.Count == 0)
{
Console.WriteLine("locals: (none)");
}
return 0;