Skip to content

OWN001 [disposable field] требует утилизировать отменённый CTS, который фикс намеренно не утилизирует (инверсия use-after-dispose) #402

Open

Description

@PhysShell

Описание

OWN001 [resource: disposable field] советует утилизировать CancellationTokenSource, который намеренно не утилизируется. Если последовать совету, вернётся реальный баг use-after-dispose.

Где встретилось. В двух реальных исправлениях C14 — xshaheen/headless-framework#1206 (JobsTaskScheduler.cs:61, _shutdownCts) и charles8051/frame-flow#727 (OpenAlAudioSink.cs:229, _clockShutdownCts):

  • фикс перестал утилизировать CTS, потому что поздние читатели токена получали ObjectDisposedException;
  • в headless фикс дополнительно подавил CA2213 и оставил комментарий с объяснением;
  • OWN001 срабатывает только на исправленной ревизии, а на дефектной (той, что утилизирует CTS) молчит.

Синтетическое воспроизведение. Четыре варианта одного класса:

вариант что в коде наблюдаемое
fixed + #pragma warning disable CA2213 CTS отменяется в DisposeAsync, намеренно не утилизируется OWN001 warning: «IDisposable field '_shutdownCts' (type 'CancellationTokenSource') is never disposed — its owner 'Scheduler' leaks it (leak)»
fixed без pragma то же OWN001 warning, тот же текст
buggy _shutdownCts.Dispose() в DisposeAsync, хотя читатели токена ещё работают ничего
контроль: CTS с таймером new CancellationTokenSource(TimeSpan.FromMinutes(5)) не утилизирован OWN001 warning, тот же текст (здесь утилизация действительно нужна)

Правило не отличает случай, где утилизация нужна (взведённый таймер или связанный родитель), от случая, где она сама и есть ошибка (конкурентные читатели токена, явное подавление). Оба случая оно называет «leak».

Как воспроизвести

mkdir repro && cat > repro/Scheduler.cs <<'EOF'
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Repro;
public sealed class Scheduler : IAsyncDisposable
{
    private readonly CancellationTokenSource _shutdownCts = new();
    private Task _worker = Task.CompletedTask;
    public CancellationToken ExecutionToken => _shutdownCts.Token;
    public void Start() => _worker = Task.Run(() => LoopAsync(_shutdownCts.Token));
    private static async Task LoopAsync(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            try { await Task.Delay(50, token).ConfigureAwait(false); }
            catch (OperationCanceledException) { }
        }
    }
    public async ValueTask DisposeAsync()
    {
        await _shutdownCts.CancelAsync().ConfigureAwait(false);
        await _worker.ConfigureAwait(false);
        // not disposed on purpose: no CancelAfter timer, late readers may still read the token
    }
}
EOF
dotnet frontend/roslyn/OwnSharp.Extractor/bin/Release/net8.0/ownsharp-extract.dll repro -o facts.json --flow-locals --stats
python -m ownlang ownir facts.json --format sarif --severity warning

Ожидаемое поведение

Для отменённого CTS без таймера и без связанного родителя предупреждения нет (или есть заметка без слова «leak»), особенно при явном подавлении CA2213. Для CTS с таймером предупреждение остаётся. На буггой форме не появляется новых предупреждений.

Версия / коммит

e7d33e3

Python

3.12.10

Activity

  1. PhysShell commented on Oct 18, 2026

    @PhysShell
    OwnerAuthor

    Промежуточный итог: вариант A (исключение в экстракторе для CTS-поля без таймера) — KILL по критерию применимости.

    • PoC проходил синтетику (20/20, все 6 мутантов пойманы), но не снял ни один из трёх реальных целевых случаев: headless-framework#1206 _shutdownCts, frame-flow#727 _clockShutdownCts, digirati#332 cancellationTokenSource. Во всех трёх System.Threading подключён через implicit usings, которые экстрактор не учитывает, поэтому тип CTS не резолвится, и символьная проверка поле не распознаёт.
    • Подтверждено: один global using System.Threading; позволяет PoC снять все три случая. Но добавлять usings на всю компиляцию нельзя: в проектах с ImplicitUsings=disable это ломает разрешение имён (Timer: CS0104). Подробности и замеры — в Extractor: project-aware semantic resolution — types from SDK implicit usings do not resolve #407.
    • Независимо от этого, docs/notes/cts-field-dispose-optional.md фиксирует решение не освобождать обычные CTS-поля от OWN001. Возврат к варианту A даже после Extractor: project-aware semantic resolution — types from SDK implicit usings do not resolve #407 требует отдельного обоснования; вариант B (уровни severity) тоже нуждается в корректном контексте компиляции.

    PoC не закоммичен. Ledger исторических находок не меняется: изменение анализа само по себе не повод их переоценивать.

    #402 заблокирован #407.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions