CodeContracts: Возможно называя метод на нулевой ссылке

У меня есть спор с инструментом статического анализа CodeContracts.

Мой код:

Screenshot

(Версия ASCII)

Инструмент говорит мне это instance.bar может быть нулевая ссылка. Я верю противоположному.

Кто прав? Как я могу доказать его неправильно?

7
задан Glorfindel 23 July 2019 в 19:11
поделиться

5 ответов

Update: Похоже, проблема в том, что инварианты не поддерживаются для статических полей.

2-е обновление: Метод, описанный ниже, является в настоящее время рекомендуемым решением.

Возможным обходным решением является создание свойства для экземпляра, которое обеспечивает инварианты, которые вы хотите сохранить. (Конечно, вам нужно Предположить их, чтобы Обеспечить было доказано). Как только вы это сделаете, вы можете просто использовать свойство, и все инварианты будут доказаны правильно.

Вот ваш пример с использованием этого метода:

class Foo
{
    private static readonly Foo instance = new Foo();
    private readonly string bar;

    public static Foo Instance
    // workaround for not being able to put invariants on static fields
    {
        get
        {
            Contract.Ensures(Contract.Result<Foo>() != null);
            Contract.Ensures(Contract.Result<Foo>().bar != null);

            Contract.Assume(instance.bar != null);
            return instance;
        }
    }

    public Foo()
    {
        Contract.Ensures(bar != null);
        bar = "Hello world!";
    }

    public static int BarLength()
    {
        Contract.Assert(Instance != null);
        Contract.Assert(Instance.bar != null);
        // both of these are proven ok

        return Instance.bar.Length;
    }
}
2
ответ дан 7 December 2019 в 12:17
поделиться

CodeContracts верен. Вам ничего не мешает установить instance.bar = null перед вызовом метода BarLength () .

2
ответ дан 7 December 2019 в 12:17
поделиться

Ваш код включает частный статический инициализированный экземпляр:

private static Foo instance = new Foo();

Предполагаете ли вы, что это означает, что конструктор экземпляра всегда будет запускаться перед доступом к любому статическому методу, что гарантирует bar был инициализирован?

В однопоточном случае, я думаю, вы правы.

Последовательность событий будет следующей:

  1. Вызов Foo.BarLength ()
  2. Статическая инициализация класса Foo (если еще не завершена)
  3. Статическая инициализация частного статический член экземпляр с экземпляром Foo
  4. Вход в Foo.BarLength ()

Однако статическая инициализация класса запускается только один раз для каждого домена приложения - и IIRC нет никакой блокировки, чтобы гарантировать, что он завершен перед вызовом любых других статических методов.

Итак, у вас может быть такой сценарий:

  1. Альфа потока: вызов Foo.BarLength ()
  2. Альфа потока: статическая инициализация класса Foo (если еще не завершена) запускает
  3. Переключение контекста
  4. Бета-версия потока: вызов Foo.BarLength ()
  5. Бета-версия потока: Нет вызова статической инициализации класса Foo , потому что это уже выполняется
  6. Бета-версия темы: вход в Foo.BarLength ()
  7. Бета-версия потока: доступ к экземпляру статического члена null

Анализатор контрактов не может знать об этом что вы никогда не будете запускать код в многопоточном режиме, поэтому следует проявлять осторожность.

2
ответ дан 7 December 2019 в 12:17
поделиться

Если вы пометите поле bar как доступное только для чтения, это должно удовлетворить статический анализатор, так как это поле никогда не будет установлено на что-либо еще после выполнения ctor.

0
ответ дан 7 December 2019 в 12:17
поделиться

Я согласен с вами. instance и bar являются частными, поэтому CodeContracts должен знать, что instance.bar никогда не имеет значения null.

-1
ответ дан 7 December 2019 в 12:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: