Что и зачем
Own.NET сегодня проверяет safety-свойства: ресурс должен быть освобождён, borrow не должен пережить владельца, короткоживущий объект не должен быть пришпилен к более длинному lifetime.
Не покрыта соседняя liveness-ошибка:
если цикл продолжает работу, он обязан продвигать состояние, управляющее его условием.
Это не проверка банального while (true) и не попытка доказать termination произвольного C#. Цель — узкий production-класс зависаний, где достижимый путь возвращается к голове цикла, не меняя распознаваемую монотонную меру и не выходя из цикла.
while(reader.Position<reader.Length){if(!TryReadNode(reader,outvarnode))continue;nodes.Add(node);}Если TryReadNode(...)=false не двигает reader.Position, failure-path бесконечно перечитывает тот же вход. Компилятор, обычные линтеры и happy-path тесты довольны; повреждённый файл кладёт production.
Текущий OwnIR моделирует while как body + back-edge для fixpoint, но не несёт guard/measure/outcome-sensitive progress contract, поэтому этот класс сейчас принципиально невыразим.
Product claim
Owen detects loops that can continue without consuming the state that controls them.
Не “general termination prover”, не temporal-logic engine и не dumb syntax rule.
Первая диагностика
PRG001 — reachable loop back-edge neither advances the recognized guard measure nor exits
Пример:
while(queue.Count>0){varjob=queue.Peek();if(!CanExecute(job))continue;// PRG001: queue.Count unchangedExecute(job);queue.Dequeue();}Ожидаемое сообщение:
PRG001 loop can repeat without progress: 'queue.Count' controls the loop, but the path
loop header -> !CanExecute(job) -> continue -> loop header neither decreases
'queue.Count' nor exits the loop. Remove/advance the item, break, or use a bounded retry policy.
Finding обязан нести ordered evidence path для SARIF codeFlows:
loop guard -> branch bypassing progress -> continue/back-edge -> unchanged measure
Scope: только распознаваемые монотонные меры
MVP поддерживает ограниченный каталог:
| Guard / measure | Progress |
|---|
i < end | i гарантированно увеличивается |
remaining > 0 | remaining гарантированно уменьшается |
queue.Count > 0 | queue.Count гарантированно уменьшается |
reader.Position < reader.Length | reader.Position гарантированно увеличивается |
| enumerator/iterator loop | успешная итерация гарантированно продвигает enumerator |
Путь к back-edge безопасен, только если:
- мера гарантированно продвинулась; или
- выполнен
break, return или throw; или - путь доказан недостижимым.
Unsupported guard / opaque arithmetic / concurrency-sensitive state => никакого definite finding. Precision floor остаётся прежним: не умеем доказать — молчим и считаем honest skip/advisory, не выдумываем Must.
Ключевая фича: outcome-sensitive progress summaries
Нужна не просто отметка “callee may mutate cursor”, а контракт по исходу:
Progress ∈ { Never, Must, May, Unknown }
TryReadNode(reader, out node):
returns true -> reader.Position MUST increase
returns false -> reader.Position NEVER increases
Тогда:
while(reader.Position<reader.Length){if(!TryReadNode(reader,outvarnode))continue;}даёт PRG001, а оба варианта ниже молчат:
if(!TryReadNode(reader,outvarnode)){reader.Position++;// discard malformed bytecontinue;}if(!TryReadNode(reader,outvarnode))break;
Summary requirements
- per-overload resolution через существующий
sig; - safe fallback к name-merged summary, если
sig отсутствует/не совпал; - SCC/fixpoint для recursive/mutually-recursive helpers;
Unknown на extern/opaque boundaries;- никакого fabricated
Must; - deterministic output и observable degradation.
Архитектура
Sidecar analysis, не новый ownership instruction zoo
Предпочтительная форма — отдельный Own.Progress analysis по образцу di.py, effects.py, obligations.py:
- frontend сообщает syntax/symbol facts;
- core вычисляет verdict;
- никаких verdict-гейтов в Roslyn extractor;
- отсутствие progress-блоков означает “analysis disabled”.
Черновая additive OwnIR shape:
{
"progress_functions": [
{
"name": "Parser.TryReadNode",
"sig": "Reader,Node&",
"measures": [
{
"symbol": "arg0.Position",
"direction": "increase",
"outcomes": {
"true": "must",
"false": "never"
}
}
]
}
],
"progress_loops": [
{
"file": "Parser.cs",
"line": 84,
"guard": {
"measure": "reader.Position",
"relation": "<",
"bound": "reader.Length",
"direction": "increase"
},
"body": []
}
]
}Это направление, не замороженная schema. До implementation PR нужен отдельный schema checkpoint: identity measures, branch outcomes, break/continue, aliasing и evidence handles должны быть определены без строковой магии.
Внутренние discriminator-вокабуляции должны быть fail-loud. Добавление top-level optional blocks может оставаться additive только при безопасном default и явной versioning-политике, согласованной с spec/OwnIR.md.
Повторное использование существующего фундамента
sig и conservative overload fallback;- method-summary / call-graph / SCC infrastructure;
- Rust generic monotone worklist из P-022;
- existing evidence + SARIF
codeFlows seam; - Python reference -> Rust parity discipline.
Не строить второй solver только потому, что человечество любит плодить почти одинаковые fixpoint-циклы.
Built-in progress specifications
Стартовый curated каталог:
Queue<T>.Dequeue() => receiver Count decreases;Stack<T>.Pop() => receiver Count decreases;List<T>.RemoveAt(...) => receiver Count decreases;i++, i += positiveConstant;remaining--, remaining -= positiveConstant;Stream.Read(...) / reader-like API => progress только при доказанном положительном результате;- iterator/enumerator primitives, если frontend может связать outcome с measure без догадок.
Project APIs подключаются через approved config, например .owen-progress.json:
{
"BrokerReader.TryMoveNext": {
"measure": "arg0.Offset",
"direction": "increase",
"outcomes": {
"true": "must",
"false": "never"
}
}
}Rules-as-data, не новый DSL для пользователей.
Proposed delivery slices
Slice 1 — direct intra-procedural progress
- распознать guard measure;
- моделировать
continue/back-edge и exits; - direct increment/decrement / curated collection consumers;
- один code:
PRG001, только definite cases; - ordered evidence path.
Slice 2 — interprocedural Try* summaries
- infer
Must/May/Never/Unknown по boolean outcome; - resolve exact overload through
sig; - SCC/fixpoint;
- extern =>
Unknown; - project config contracts.
Slice 3 — real-world oracle
- mine parser/import/queue-processing loops;
- validate on at least one internal legacy target and one OSS C# target;
- manually classify every new finding before enabling any gate;
- retain “own-only 0 unexplained” precision discipline.
Slice 4 — optional runtime confirmation (later, separate scope)
A watchdog/stack-sample artifact may later correlate static PRG001 with repeated runtime stacks at the same loop and produce confirmed-progress-stall. This is explicitly not part of the first implementation.
Phase 1 acceptance contract
queue.Count > 0 + reachable continue bypassing Dequeue() => exactly one PRG001.- Every reachable back-edge contains definite
Dequeue() => silent. - Non-progress branch ending in
break, return, or throw => silent. i < end with i++ on every back-edge => silent.i < end with one branch bypassing i++ via continue => PRG001.- Unsupported guard/opaque mutation => no definite finding; skip/degradation observable.
- Finding contains loop header, bypass branch, and back-edge as ordered evidence.
- Existing OwnIR/ownership/lifetime/DI/effects/obligations fixtures remain unchanged.
- Unknown progress event vocabulary fails loudly, never silently skips.
- Deterministic ordering and stable line-free message fingerprint.
Phase 2 acceptance contract
- First-party
Try* method infers per-outcome progress. false => Never followed by continue => PRG001.false => Never followed by break => silent.true/false => Must => safe on both continuing branches.- Mixed paths =>
May, not fabricated Must. - Exact overload selected via
sig; unmatched/missing sig degrades conservatively. - Recursive and mutually-recursive helpers converge correctly.
- Extern/opaque callees remain
Unknown and do not create definite findings. - Python and Rust agree on
(path, line, code, evidence path). - Real-world sweep records TP/FP/uncertain classification and blocks rollout on unexplained removals/additions.
Repository boundary
- Own.NET: facts, Roslyn extraction, Python reference analysis, Rust parity, PRG diagnostics, case studies.
- OwnAudit: normalization/category/severity, baseline ratchet, dashboard/SARIF consumption, optional later runtime correlation.
- Do not implement a second detector in OwnAudit.
Companion OwnAudit consumer issue will be linked here after creation.
Explicit non-goals
- proving termination of arbitrary C#;
- arbitrary ranking functions or symbolic arithmetic;
- deadlock/livelock under concurrency;
- async scheduling/fairness proofs;
- warning on every
while (true); - general temporal logic;
- auto-fixing loops in v1;
- runtime collector in the first implementation.
Definition of success
A convincing demo is not “we detect a suspicious loop”. It is:
PRG001 at Parser.cs:84
loop guard reader.Position < reader.Length
-> TryReadNode returned false
-> continue
-> back-edge
reader.Position unchanged
The reviewer can click through the exact non-progress cycle and understand the fix without reverse-engineering the analyzer’s mood.
Что и зачем
Own.NET сегодня проверяет safety-свойства: ресурс должен быть освобождён, borrow не должен пережить владельца, короткоживущий объект не должен быть пришпилен к более длинному lifetime.
Не покрыта соседняя liveness-ошибка:
Это не проверка банального
while (true)и не попытка доказать termination произвольного C#. Цель — узкий production-класс зависаний, где достижимый путь возвращается к голове цикла, не меняя распознаваемую монотонную меру и не выходя из цикла.Если
TryReadNode(...)=falseне двигаетreader.Position, failure-path бесконечно перечитывает тот же вход. Компилятор, обычные линтеры и happy-path тесты довольны; повреждённый файл кладёт production.Текущий OwnIR моделирует
whileкак body + back-edge для fixpoint, но не несёт guard/measure/outcome-sensitive progress contract, поэтому этот класс сейчас принципиально невыразим.Product claim
Не “general termination prover”, не temporal-logic engine и не dumb syntax rule.
Первая диагностика
Пример:
Ожидаемое сообщение:
Finding обязан нести ordered evidence path для SARIF
codeFlows:Scope: только распознаваемые монотонные меры
MVP поддерживает ограниченный каталог:
i < endiгарантированно увеличиваетсяremaining > 0remainingгарантированно уменьшаетсяqueue.Count > 0queue.Countгарантированно уменьшаетсяreader.Position < reader.Lengthreader.Positionгарантированно увеличиваетсяПуть к back-edge безопасен, только если:
break,returnилиthrow; илиUnsupported guard / opaque arithmetic / concurrency-sensitive state => никакого definite finding. Precision floor остаётся прежним: не умеем доказать — молчим и считаем honest skip/advisory, не выдумываем
Must.Ключевая фича: outcome-sensitive progress summaries
Нужна не просто отметка “callee may mutate cursor”, а контракт по исходу:
Тогда:
даёт
PRG001, а оба варианта ниже молчат:Summary requirements
sig;sigотсутствует/не совпал;Unknownна extern/opaque boundaries;Must;Архитектура
Sidecar analysis, не новый ownership instruction zoo
Предпочтительная форма — отдельный
Own.Progressanalysis по образцуdi.py,effects.py,obligations.py:Черновая additive OwnIR shape:
{ "progress_functions": [ { "name": "Parser.TryReadNode", "sig": "Reader,Node&", "measures": [ { "symbol": "arg0.Position", "direction": "increase", "outcomes": { "true": "must", "false": "never" } } ] } ], "progress_loops": [ { "file": "Parser.cs", "line": 84, "guard": { "measure": "reader.Position", "relation": "<", "bound": "reader.Length", "direction": "increase" }, "body": [] } ] }Это направление, не замороженная schema. До implementation PR нужен отдельный schema checkpoint: identity measures, branch outcomes,
break/continue, aliasing и evidence handles должны быть определены без строковой магии.Внутренние discriminator-вокабуляции должны быть fail-loud. Добавление top-level optional blocks может оставаться additive только при безопасном default и явной versioning-политике, согласованной с
spec/OwnIR.md.Повторное использование существующего фундамента
sigи conservative overload fallback;codeFlowsseam;Не строить второй solver только потому, что человечество любит плодить почти одинаковые fixpoint-циклы.
Built-in progress specifications
Стартовый curated каталог:
Queue<T>.Dequeue()=> receiverCountdecreases;Stack<T>.Pop()=> receiverCountdecreases;List<T>.RemoveAt(...)=> receiverCountdecreases;i++,i += positiveConstant;remaining--,remaining -= positiveConstant;Stream.Read(...)/ reader-like API => progress только при доказанном положительном результате;Project APIs подключаются через approved config, например
.owen-progress.json:{ "BrokerReader.TryMoveNext": { "measure": "arg0.Offset", "direction": "increase", "outcomes": { "true": "must", "false": "never" } } }Rules-as-data, не новый DSL для пользователей.
Proposed delivery slices
Slice 1 — direct intra-procedural progress
continue/back-edge и exits;PRG001, только definite cases;Slice 2 — interprocedural
Try*summariesMust/May/Never/Unknownпо boolean outcome;sig;Unknown;Slice 3 — real-world oracle
Slice 4 — optional runtime confirmation (later, separate scope)
A watchdog/stack-sample artifact may later correlate static
PRG001with repeated runtime stacks at the same loop and produceconfirmed-progress-stall. This is explicitly not part of the first implementation.Phase 1 acceptance contract
queue.Count > 0+ reachablecontinuebypassingDequeue()=> exactly onePRG001.Dequeue()=> silent.break,return, orthrow=> silent.i < endwithi++on every back-edge => silent.i < endwith one branch bypassingi++viacontinue=>PRG001.Phase 2 acceptance contract
Try*method infers per-outcome progress.false => Neverfollowed bycontinue=>PRG001.false => Neverfollowed bybreak=> silent.true/false => Must=> safe on both continuing branches.May, not fabricatedMust.sig; unmatched/missingsigdegrades conservatively.Unknownand do not create definite findings.(path, line, code, evidence path).Repository boundary
Companion OwnAudit consumer issue will be linked here after creation.
Explicit non-goals
while (true);Definition of success
A convincing demo is not “we detect a suspicious loop”. It is:
The reviewer can click through the exact non-progress cycle and understand the fix without reverse-engineering the analyzer’s mood.