Skip to content

Commit cbe2fca

Browse files
committed
ownership-lab H-24: census-only admissibility seam (OWEN_H24_CENSUS=<path>: one JSON line per disposable local with nested simple-assignment writes; preconditions of h24-prereg-v1 evaluated: one top-level if-chain / switch, the recursive arm rule, Roslyn definite assignment on exit, write kinds via the oracle / acquire predicates, references inside the construct; a callee diagnostic per write); no lowering change, OFF facts byte-identical
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Am9eQwzNfbugH72eVKetC2
1 parent a2aacd2 commit cbe2fca

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

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

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3007,6 +3007,92 @@ static bool H23AAcquireShaped(ExpressionSyntax? rhs, SemanticModel model, string
30073007
return ws;
30083008
}
30093009

3010+
// ===== ownership-semantics-lab H-24 (registered before this code; h24-prereg-v1.json): the restricted single-obligation
3011+
// merge ADMISSIBILITY census. Census only: nothing is lowered differently. One JSON line per disposable local whose
3012+
// writes are nested in control flow, with the frozen preconditions evaluated: every write a simple-assignment statement,
3013+
// no initializer obligation, all writes inside ONE top-level if-chain / switch, the recursive arm rule (one direct write
3014+
// per arm, or an exiting arm, or exactly one nested construct obeying the same rule), Roslyn definite assignment on exit,
3015+
// the write kinds (one ownership class), references inside the construct. Behind OWEN_H24_CENSUS=<path>.
3016+
static void H24Classify(VariableDeclaratorSyntax v, BlockSyntax mbody, SemanticModel model)
3017+
{
3018+
if (model.GetDeclaredSymbol(v) is not ILocalSymbol local) return;
3019+
var writes = new List<ExpressionStatementSyntax>(); var refs = new List<IdentifierNameSyntax>(); var reasons = new SortedSet<string>(StringComparer.Ordinal);
3020+
foreach (var id in mbody.DescendantNodes().OfType<IdentifierNameSyntax>())
3021+
{
3022+
if (!SymbolEqualityComparer.Default.Equals(model.GetSymbolInfo(id).Symbol, local)) continue;
3023+
if (id.Parent is AssignmentExpressionSyntax asg && asg.Left == id)
3024+
{
3025+
if (!asg.IsKind(SyntaxKind.SimpleAssignmentExpression) || asg.Parent is not ExpressionStatementSyntax es) { reasons.Add("non_simple_write"); continue; }
3026+
if (id.Ancestors().TakeWhile(x => x != mbody).Any(x => x is AnonymousFunctionExpressionSyntax or LocalFunctionStatementSyntax)) { reasons.Add("write_in_lambda"); continue; }
3027+
writes.Add(es);
3028+
}
3029+
else if (id.Parent is RefExpressionSyntax || id.Parent is ArgumentSyntax { RefKindKeyword.RawKind: not 0 }) reasons.Add("ref_alias");
3030+
else refs.Add(id);
3031+
}
3032+
if (writes.Count == 0) return;
3033+
StatementSyntax? Top(SyntaxNode n) => n.Ancestors().TakeWhile(x => x != mbody).OfType<StatementSyntax>().LastOrDefault();
3034+
var tops = writes.Select(Top).ToList();
3035+
if (tops.All(t => t is null)) return; // straight-line writes: H-23A's class, not H-24's
3036+
if (tops.Any(t => t is null)) reasons.Add("write_outside_construct");
3037+
var distinct = tops.Where(t => t is not null).Distinct().ToList();
3038+
if (distinct.Count > 1) reasons.Add("writes_in_multiple_constructs");
3039+
var S = distinct.Count == 1 && !tops.Any(t => t is null) ? distinct[0] : null;
3040+
var ckind = S switch { IfStatementSyntax => "if_chain", SwitchStatementSyntax => "switch", TryStatementSyntax => "try", UsingStatementSyntax => "using", LockStatementSyntax => "lock",
3041+
WhileStatementSyntax or ForStatementSyntax or ForEachStatementSyntax or DoStatementSyntax => "loop", BlockSyntax => "block", null => "none", _ => "other" };
3042+
if (S is not null && ckind is not ("if_chain" or "switch")) reasons.Add("construct_" + ckind);
3043+
var init = v.Initializer?.Value; string initKind;
3044+
if (init is null || init.IsKind(SyntaxKind.NullLiteralExpression) || init.IsKind(SyntaxKind.DefaultLiteralExpression)) initKind = "none_or_null";
3045+
else { initKind = H23AAcquireShaped(init, model, "merge_initializer") ? "prior_live_acquire" : "borrowed_or_other"; reasons.Add("initializer_" + initKind); }
3046+
var W = new HashSet<ExpressionStatementSyntax>(writes); var depth = 0;
3047+
IEnumerable<StatementSyntax> Stmts(StatementSyntax? a) => a is BlockSyntax b ? b.Statements : a is null ? Array.Empty<StatementSyntax>() : new[] { a };
3048+
bool ArmsOk(StatementSyntax c, int d)
3049+
{
3050+
depth = Math.Max(depth, d); var arms = new List<IEnumerable<StatementSyntax>>();
3051+
if (c is IfStatementSyntax i0)
3052+
{
3053+
for (var i = i0; ;) { arms.Add(Stmts(i.Statement)); if (i.Else?.Statement is IfStatementSyntax ei) { i = ei; continue; } arms.Add(Stmts(i.Else?.Statement)); break; }
3054+
}
3055+
else if (c is SwitchStatementSyntax sw) foreach (var sec in sw.Sections) arms.Add(sec.Statements);
3056+
else return false;
3057+
foreach (var arm in arms)
3058+
{
3059+
var list = arm.ToList();
3060+
var direct = list.Count(st => st is ExpressionStatementSyntax es && W.Contains(es));
3061+
var holders = list.Where(st => !(st is ExpressionStatementSyntax es && W.Contains(es)) && st.DescendantNodes().OfType<ExpressionStatementSyntax>().Any(W.Contains)).ToList();
3062+
if (holders.Count == 0 && direct <= 1) continue; // one direct write, or none (the arm must exit: definite assignment decides)
3063+
if (direct == 0 && holders.Count == 1 && holders[0] is IfStatementSyntax or SwitchStatementSyntax && ArmsOk(holders[0], d + 1)) continue;
3064+
return false;
3065+
}
3066+
return true;
3067+
}
3068+
var armsOk = S is not null && ckind is "if_chain" or "switch" && ArmsOk(S, 1);
3069+
if (S is not null && ckind is "if_chain" or "switch" && !armsOk) reasons.Add("arm_rule");
3070+
bool? daOnExit = null;
3071+
if (S is not null) { try { var df = model.AnalyzeDataFlow(S); daOnExit = df.Succeeded && df.DefinitelyAssignedOnExit.Any(x => SymbolEqualityComparer.Default.Equals(x, local)); } catch { } }
3072+
if (daOnExit != true) reasons.Add("not_definitely_assigned_on_exit");
3073+
string Kind(ExpressionSyntax rhs) =>
3074+
rhs.IsKind(SyntaxKind.NullLiteralExpression) || rhs.IsKind(SyntaxKind.DefaultLiteralExpression) ? "null"
3075+
: rhs is ObjectCreationExpressionSyntax or ImplicitObjectCreationExpressionSyntax ? (model.GetTypeInfo(rhs).Type is { } t && ImplementsIDisposable(t) && !IsDisposeOptional(t) && !HasEmptyDisposeBody(t) ? "new_disposable" : "new_other")
3076+
: IsPoolRent(rhs, model) ? "pool_rent" : IsOwningFactory(rhs, model) ? "owning_factory"
3077+
: ReOracle.ReturnsFreshOwned(rhs, model, "merge") ? "trusted_row"
3078+
: IsFirstPartyDisposableFactory(rhs, model, out _, out _) ? "first_party_factory" : "other";
3079+
string Callee(ExpressionSyntax rhs) // census diagnostic for the non-fresh kinds: what the write's call resolved to
3080+
{
3081+
if (rhs is not InvocationExpressionSyntax inv) return rhs.Kind().ToString();
3082+
var si = model.GetSymbolInfo(inv);
3083+
return si.Symbol is IMethodSymbol m ? $"{m.ContainingType.ToDisplayString()}.{m.Name}/{m.Parameters.Length}" : $"unresolved:{si.CandidateReason}:{string.Join('|', si.CandidateSymbols.Select(c => c.ToDisplayString()))}";
3084+
}
3085+
var wk = writes.Select(w => new { line = LineOf(w), kind = Kind(((AssignmentExpressionSyntax)w.Expression).Right), callee = Callee(((AssignmentExpressionSyntax)w.Expression).Right) }).ToList();
3086+
var owned = wk.All(x => x.kind is "trusted_row" or "new_disposable" or "owning_factory" or "first_party_factory"); var pool = wk.All(x => x.kind == "pool_rent");
3087+
var cls = owned ? "owned" : pool ? "pool" : "mixed_or_not_fresh"; if (!owned && !pool) reasons.Add("write_kind_" + string.Join("+", wk.Select(x => x.kind).Distinct().OrderBy(x => x, StringComparer.Ordinal)));
3088+
var refsIn = S is null ? 0 : refs.Count(r => r.Ancestors().Contains(S)); var refsAfter = refs.Count - refsIn;
3089+
var relaxed = reasons.Count == 0; var strict = relaxed && refsIn == 0;
3090+
var member = v.Ancestors().OfType<MemberDeclarationSyntax>().FirstOrDefault();
3091+
H24.Emit(new { file = v.SyntaxTree.FilePath, line = LineOf(v), local = v.Identifier.Text, type = local.Type.ToDisplayString(), member = member is MethodDeclarationSyntax md ? md.Identifier.Text : member?.Kind().ToString(),
3092+
construct = ckind, construct_line = S is null ? 0 : LineOf(S), depth, arms_ok = armsOk, definitely_assigned_on_exit = daOnExit, initializer = initKind, writes = wk, write_count = writes.Count,
3093+
owned_class = cls, trusted_row_writes = wk.Count(x => x.kind == "trusted_row"), refs_inside = refsIn, refs_after = refsAfter, reasons = reasons.ToArray(), admissible_strict = strict, admissible_relaxed = relaxed });
3094+
}
3095+
30103096
static ExpressionSyntax? LabNullInitSingleAssignment(VariableDeclaratorSyntax v, BlockSyntax mbody, SemanticModel model)
30113097
{
30123098
if (model.GetDeclaredSymbol(v) is not ILocalSymbol local)
@@ -7940,6 +8026,13 @@ or ImplicitObjectCreationExpressionSyntax } init
79408026
else if (IsOwningFactory(hAcq[0], model) || ReOracle.ReturnsFreshOwned(hAcq[0], model, "assignment")) mintedFactories.Add(v.Identifier.Text);
79418027
}
79428028
}
8029+
// H-24 (OWEN_H24_CENSUS=<path>; h24-prereg-v1.json): admissibility census of the restricted merge, census only
8030+
if (H24.Enabled)
8031+
foreach (var ld in mbody.DescendantNodes().OfType<LocalDeclarationStatementSyntax>())
8032+
if (ld.UsingKeyword == default)
8033+
foreach (var v in ld.Declaration.Variables)
8034+
if (model.GetDeclaredSymbol(v) is ILocalSymbol l4 && ImplementsIDisposable(l4.Type) && !IsDisposeOptional(l4.Type) && !HasEmptyDisposeBody(l4.Type))
8035+
H24Classify(v, mbody, model);
79438036
// `using (IMemoryOwner owner = MemoryPool.Rent(...)) { … }` STATEMENT form: track the owner
79448037
// too, so its returned view dangles after the scope-exit dispose (the desugar mirrors the
79458038
// `using` DECLARATION form handled in the loop above).
@@ -8575,6 +8668,15 @@ static void Walk(JsonArray arr, Dictionary<string, (int n, bool tracked)> ver)
85758668
}
85768669
}
85778670

8671+
// ===== ownership-semantics-lab H-24 (registered before this code): the census sink of H24Classify. Census only.
8672+
static class H24
8673+
{
8674+
internal static readonly string? Path = Environment.GetEnvironmentVariable("OWEN_H24_CENSUS");
8675+
internal static readonly bool Enabled = !string.IsNullOrEmpty(Path);
8676+
static readonly object Lock = new();
8677+
internal static void Emit(object o) { try { lock (Lock) File.AppendAllText(Path!, JsonSerializer.Serialize(o) + "\n"); } catch { } }
8678+
}
8679+
85788680
// ===== ownership-semantics-lab H-19 (registered before this code): a NOT-NULL guard on a tracked local
85798681
// (`if (x != null) S`, `if (null != x) S`, `if (x is not null) S`, `if (x is { }) S`, no else) whose lowered
85808682
// branch names only x is lowered as the branch itself: a null handle carries no obligation, so the guard

0 commit comments

Comments
 (0)