Подтвердить что ты не робот

Контракты с кодом С#: что можно проверить статически и что не может?

Я могу сказать, что я хорошо знаком с Code Contracts: я прочитал и понял большую часть руководство пользователя и использовал они довольно долгое время, но у меня все еще есть вопросы. Когда я ищу SO для "незавершенных кодовых контрактов", существует довольно много хитов, и все спрашивают, почему их конкретное утверждение не может быть проверено статически. Хотя я мог бы сделать то же самое и опубликовать свой конкретный сценарий (который есть btw:

enter image description here),

Я бы предпочел понять, почему любое условие Кодового Контракта может или не может быть доказано. Иногда меня впечатляет то, что он может доказать, а иногда я... ну... сказать это вежливо: определенно не впечатлило. Если я хочу это понять, я хотел бы знать механизмы, используемые статическим контролером. Я уверен, что я буду учиться на опыте, но я распыляю заявления Contract.Assume повсюду, чтобы предупредить об этом, и я чувствую, что это не то, что подразумевается в кодовых контрактах. Googling не помог мне, поэтому я хочу спросить вас, ребята, о ваших впечатлениях: какие (неочевидные) модели вы видели? И что заставило вас увидеть свет?

4b9b3361

Ответ 1

Контракт в вашем строительстве не выполняется. Поскольку вы ссылаетесь на поле объектов (this.data), другие потоки могут иметь доступ к полю и могут изменять его значение между Соглашением и разрешением первого параметра и третьим разрешением параметра. (например, они могут быть тремя совершенно разными массивами.)

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

var localData = this.data;
if (localData == null) return;
byte[] newData = new byte[localData.Length]; // Or whatever the datatype is.
Array.Copy(localData, newData, localData.Length); // Now, this cannot fail.

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

Надеюсь, это приведет вас к ответу на ваш вопрос. Я не мог ответить на ваш вопрос напрямую, потому что у меня нет доступа к версии Visual Studio, которая включает в себя статическую проверку. (Я на VS2008 Pro.) Мой ответ основан на том, что мой собственный визуальный осмотр кода будет заключен, и похоже, что статический контролер транзакций использует аналогичные методы. Я заинтригован! Мне нужно достать мне одну из них.:-D

ОБНОВЛЕНИЕ: (Множество спекуляций)

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

Под "локальными переменными" я имею в виду переменную, к которой не может обращаться ни один другой поток. Это будет включать любые переменные, объявленные в методе или переданные в качестве параметра, если параметр не украшен ref или out, или переменная не будет записана анонимным методом.

Если локальная переменная является типом значений, то ее поля также являются локальными переменными (и так далее рекурсивно).

Если локальная переменная является ссылочным типом, то только сама ссылка, а не ее поля, может считаться локальной переменной. Это справедливо даже для объекта, построенного в методе, поскольку сам конструктор может утечка ссылки на построенный объект (например, для статического набора для кэширования).

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

Исключение 1: поскольку строки и массивы известны тем, что среда выполнения является неизменяемой, их свойства (такие как длина) подлежат анализу, если сама строка или переменная массива являются локальными. Это не включает содержимое массива, которые изменяются другими потоками.

Исключение 2: конструктор массива может быть известен по времени выполнения, чтобы не утечка каких-либо ссылок на построенный массив. Поэтому массив, который сконструирован внутри тела метода и не просочился вне метода (передан как параметр другому методу, назначается нелокальной переменной и т.д.), Содержит элементы, которые также могут считаться локальными переменными.

Эти ограничения кажутся довольно обременительными, и я могу представить несколько способов, которыми это можно было бы улучшить, но я не знаю, что было сделано. Вот некоторые другие вещи, которые теоретически можно было бы сделать с помощью статической проверки. Кто-нибудь, у кого есть это, должен проверить, что сделано, а что нет:

  • Он может определить, не вызывает ли конструктор утечки каких-либо ссылок на объект или его поля и рассматривает поля любого объекта, сконструированного таким образом как локальные переменные.
  • Анализ без утечек может быть выполнен по другим методам, чтобы определить, может ли ссылочный тип, переданный методу, локально после вызова этого метода.
  • Переменные, украшенные ThreadStatic или ThreadLocal, могут считаться локальными переменными.
  • Параметры могут быть заданы, чтобы игнорировать возможность использования отражения для изменения значений. Это позволит считать частные поля readonly для ссылочных типов или статических частных полей readonly считающимися неизменяемыми. Кроме того, когда эта опция включена, частная или внутренняя переменная X, которая только когда-либо доступна в конструкции lock(X){ /**/ } и которая не протекает, может считаться локальной переменной. Тем не менее, эти вещи, по сути, уменьшат надежность статической проверки, поэтому, если это будет возможно.

Еще одна возможность, которая может открыть много нового анализа, будет декларативно назначать переменные и методы, которые их используют (и так далее рекурсивно) для конкретного уникального потока. Это было бы существенным дополнением к языку, но это могло бы стоить того.

Ответ 2

Короткий ответ заключается в том, что статический анализатор кода, по-видимому, очень ограничен. Например, он не обнаруживает

readonly string name = "I'm never null";

как инвариант. Из того, что я могу собрать на форумах MSDN, он анализирует каждый метод сам по себе (по соображениям производительности, а не по мнению, что он может стать намного медленнее), что ограничивает его знание при проверке кода.

Чтобы достичь баланса между академически высокой целью доказать правильность и быть в состоянии выполнить работу, я прибегал к оформлению отдельных методов (или даже классов по мере необходимости) с помощью

[ContractVerification(false)]

а не опрыскивать логику множеством утверждений. Это не может быть наилучшей практикой для использования CC, но это дает возможность избавиться от предупреждений, не снимая ни одной из статических параметров проверки. Чтобы не потерять проверки до/после состояния для таких методов, я обычно добавляю заглушку с желаемыми условиями и затем вызываю исключенный метод для выполнения фактической работы.

Моя собственная оценка контрактов с кодом заключается в том, что это здорово, если вы используете только официальные библиотеки баз данных и не имеете много устаревшего кода (например, при запуске нового проекта). Что-нибудь еще и это смешанная сумка удовольствия и боли.