Skip to content

fix(roslyn): same-named locals in sibling scopes are no longer conflated (#408) - #409

Merged
PhysShell merged 1 commit into
mainfrom
fix/408-sibling-locals
Oct 18, 2026
Merged

PhysShell merged 1 commit into
mainfrom
fix/408-sibling-locals

Conversation

@PhysShell

Copy link
Copy Markdown
Owner

Что и зачем

Flow-проход --flow-locals идентифицирует локалы по имени. C# разрешает двум разным локалам иметь одно имя, если их области видимости не пересекаются: две ветки, два блока, ветка и цикл. Когда отслеживается только один из них, ссылки на второй становились операциями над первым:

Исправление только в экстракторе:

  • кандидаты в отслеживаемые запоминаются по объявлению (declarator);
  • для каждого неотслеживаемого одноимённого локала берётся его область видимости: блок, оператор foreach / for / using / switch / fixed, для out var и паттернов — их блок. Внутри неё имя убирается из tracked.

Пересекаться области двух одноимённых локалов C# не даёт (CS0128 / CS0136), поэтому внутри такой области имя может означать только неотслеживаемый локал. OwnIR и оба ядра не менялись.

Тип изменения

  • feat — новая возможность
  • fix — исправление бага
  • docs — документация
  • refactor / chore / test / ci — без изменения поведения (новая регрессия + шаг CI)

Как проверено

  • OWN_TIERB_REQUIRED=1 python tests/test_extractor_sibling_locals.py, 6 критериев:

    • соседние ветки, и находка в соседнем методе не теряется;
    • последовательные блоки;
    • foreach и out var;
    • оба одноимённых локала отслеживаются — утечка второго остаётся;
    • контроль: настоящий use-after-return сохраняется;
    • try-блок.

    На main тест красный (ядро падает), на ветке зелёный. Шесть мутантов, в каждом убрана одна часть исправления, — все пойманы.

  • Python- и Rust-ядро (own-cli ownir, release) на воспроизведении:

    • на main оба падают одинаково;
    • на ветке оба выдают одни и те же три ожидаемые находки.
  • Факты побайтно совпадают с main (6b6a0ab):

    • на frontend/roslyn/samples, protocol-samples, heap-effects-samples в обоих режимах; исключение — protocol-samples с --flow-locals, где экстрактор и на main, и на ветке завершается с кодом 2 и фактов не пишет;
    • на 143 из 144 реальных деревьев (корпус исследования: 60 пар base/fix, 15 holdout, 13 sweep).
  • Единственное изменившееся дерево — MessagePack-CSharp. Там поменялась ровно одна функция, ToLZ4BinaryCore: из цикла другой ветки ушёл лишний use lz4Span. Документ, на котором ядро раньше падало, теперь анализируется: 8 находок и 4 OWN050, у обоих ядер одинаково.

  • Без регрессий в OWN_TIERB_REQUIRED=1:

    • test_extractor_columns, test_extractor_deep_flow, test_guarded_consume;
    • test_ownir (292/292), test_certify (114/114);
    • test_p037_sidecar, test_stage3_surfaces.
  • ruff check . — чисто.

  • python tests/run_tests.py целиком локально не проходит из-за окружения — так же, как в fix(roslyn): a deep if/else-if chain no longer aborts the whole extraction (#401) #403.

Связанные issue

Closes #408

Чеклист

  • изменение покрыто тестом/селфтестом (или объяснено, почему нет)
  • README/docs обновлены при необходимости — не требуется: поведение приведено к семантике C#
  • коммиты в conventional-commit стиле (feat:, fix:, docs: …)

Границы исправления

  • Параметры лямбд и локальных функций, совпадающие по имени с отслеживаемым локалом, не учитываются.
  • Когда оба одноимённых локала отслеживаются, у второго, никогда не освобождаемого, ядро пишет «may not be disposed on every path» вместо «is never disposed». Находка сохраняется, поведение не изменилось: ядро склеивает два acquire под одним именем на слиянии ветвей. Исправление этого требует уникальных имён в фактах, а значит изменения в сообщениях; здесь оно не делается.
  • Escape-анализ кандидатов тоже по-прежнему работает по имени. Если одноимённый неотслеживаемый локал «уходит» (возвращается или передаётся), отслеживаемый может потерять отслеживание. Это даёт пропуск находки, а не падение и не ложную находку; в корпусе не встретилось.

🤖 Generated with Claude Code

…ted (#408)

The --flow-locals pass keys locals by name. C# lets two different locals share
a name when their scopes are disjoint, so a reference to an untracked local
named like a tracked one became an op on the tracked local:

- in a sibling branch, a `use` with no `acquire` on that path made the core
  abort the whole document (internal OWN030 "undefined name", exit 2;
  MessagePack-CSharp ToLZ4BinaryCore);
- in a later block, a `use` after the tracked local's release was a false
  OWN002 use-after-return.

Candidates are now remembered by declarator. Every scope that declares an
untracked same-named local (its block, or the foreach / for / using / switch /
fixed statement it belongs to; `out var` / pattern designations to their
block) hides that name from `tracked` inside it. Scopes of two same-named
locals never overlap (CS0128 / CS0136), so inside that scope the name can only
mean the untracked local. Extractor-only: OwnIR and both cores are unchanged.

Closes #408

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PhysShell
PhysShell merged commit 10df475 into main Oct 18, 2026
83 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Core crash (internal OWN030 undefined name) on same-named locals in sibling scopes under --flow-locals

1 participant