You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit cbe2fca
Browse filesBrowse the repository at this point in the historyBrowse files
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
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;
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");
: 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(),
0 commit comments