Связанные переменные являются одной из основных проблем статического анализа.
Данная статья посвящена анализу этой темы и рассказу о том, как разработчики PVS-Studio борются с ложными срабатываниями, появляющимися из-за различных связей.
О чем эта статья?
Команда разработчиков PVS-Studio постоянно работает над улучшением качества анализа.Но мало просто добавить улучшение в продукт – об этом, конечно, нужно говорить! И сегодня мы решили поговорить о том, как связи между переменными мешают работе статического анализатора и как C#-анализатор PVS-Studio пытается их учитывать.
Наслаждайся чтением!
Немного об анализе потоков данных
Начнем немного издалека.Одним из наиболее важных механизмов, используемых C#-анализатором PVS-Studio, является анализ потоков данных.
Не вдаваясь в подробности, это технология, позволяющая анализатору отслеживать возможные значения переменных.
В PVS-Studio анализ потоков данных тесно взаимодействует с другими технологиями, о которых вы можете прочитать Здесь .
Числовые и логические типы
Самый простой способ продемонстрировать возможности анализа потоков данных — использовать числовые и логические переменные:Анализ потоков данных позволяет PVS-Studio рассчитать точное значение флаг и сообщить, что проверка не имеет смысла, т.к.int a = 5; int b = 3; bool flag = a > b; if (flag) // always true { .
}
а всегда больше б .
Во многих случаях выражения и, в частности, переменные могут иметь любое значение из некоторого множества.
Например: void MyFunc(bool flag)
{
int a = flag ? 1 : 10;
bool greater = a > 5;
if (greater)
Console.WriteLine("a > 5");
if (a == 5)
Console.WriteLine("a = 5");
}
В зависимости от значения, которое будет передано в параметр флаг , переменная а будет равно 1 или 10. Следовательно, значение переменной больший может быть, как истинный , так ЛОЖЬ .
Поэтому синтаксический анализатор не будет рассматривать проверку значения больший бессмысленно.
С другой стороны, PVS-Studio точно знает, что а никогда не равняется 5. Поэтому анализатор выдаст предупреждение: В3022 Выражение 'a == 5' всегда ложно.
В некоторых случаях «лишние» проверки появляются из-за опечаток или логических ошибок.
Например, иногда программист проверяет значение не той переменной.
Анализ нулевого состояния
С переменными ссылочных типов дела обстоят немного иначе.Здесь парсер ищет возможность равенства нулевой , то есть он выполняет анализ нулевого состояния.
Каждая переменная ссылочного типа, с точки зрения PVS-Studio, может находиться в одном из 4 состояний:
- Неизвестно – когда нет информации о том, может ли переменная быть равна нулевой или нет. Все переменные ссылочных типов имеют это состояние по умолчанию;
- Null – когда переменная точно равна нулевой ;
- NotNull – когда переменная не совсем равна нулевой ;
- PotentialNull – когда в некоторых случаях переменная точно равна нулевой .
void TestReferences(bool flag)
{
string potentialNullStr = flag ? "not null" : null;
_ = potentialNullStr.GetHashCode();
}
В момент звонка GetHashCode переменная потенциалNullStr может быть или не быть равным нулевой .
Разыменование потенциально значимой ссылки нулевой , может привести к выдаче исключения, поэтому анализатор выдаст соответствующее предупреждение: В3080 Возможное нулевое разыменование.
Рассмотрите возможность проверки «potentialNullStr».
Что делать? Самое простое — еще проверить, что переменная не равна нулевой : void TestReferences(bool flag)
{
string potentialNullStr = flag ? "not null" : null;
if (potentialNullStr != null)
_ = potentialNullStr.GetHashCode();
}
Анализатор легко подсчитывает, что находится в организме оператора.
если переменная потенциалNullStr определенно не равны нулевой , что означает вызов GetHashCode не приведет к созданию исключения.
Связанные переменные
Все бы ничего, но иногда в реальном коде проверка нулевой производится более сложным способом.И нет, мы не говорим о нулевой -условный оператор – его поддержка – более или менее тривиальная задача.
В простейшем случае нам просто нужно не ругаться, если к члену обращаются через "?.
".
Настоящая трудность заключается в проверке нулевой используя связанную переменную.
Чтобы лучше понять тему, вернемся к предыдущему примеру: public void TestReferences(bool flag)
{
string potentialNull = flag ? "not null" : null;
if (potentialNull != null)
_ = potentialNull.GetHashCode();
}
К переменной потенциалNull можно записать нулевой .
Однако перед разыменованием проводится проверка, и анализ потока данных это учитывает. А что, если чек на нулевой это делается неявно? public void TestReferences(bool flag)
{
string potentialNull = flag ? "not null" : null;
if (flag)
_ = potentialNull.GetHashCode();
}
С точки зрения статического анализатора значение флаг неизвестно, что означает потенциалNull можно записать нулевой .
Дальнейшее обследование не дает никакой информации о потенциалNull , потому что эта переменная даже не используется в условии.
Соответственно, анализатор предупредит о возможном разыменовании нулевой ссылки.
Фактически, если флаг = правда , затем в потенциалNull строка написана.
Проверки равенства нулевой как бы нет, но никакого разыменования нулевой ссылки здесь быть не может. Связи можно создавать разными способами.
Ранее мы рассмотрели пример с переменными логического и ссылочного типов.
Однако вообще всё может зависеть от чего угодно.
Например, ниже показана связь между двумя переменными ссылочных типов: public void RelatedVariables2(string param)
{
string? potentialNull = param != null ? "not null" : null;
if (param != null)
{
_ = potentialNull.GetHashCode();
}
}
Переменная потенциалNull равно нулевой только если параметр равно нулевой .
Другими словами, либо обе переменные равны нулевой , или оба не равны.
Соответственно, вызов GetHashCode здесь никогда не вызовет исключение.
Но почему-то я зациклился на переменных ссылочных типов.
Давайте посмотрим на другой пример: public void RelatedVariables3(int a, int[] array)
{
int b = 0;
int index = -1;
if (a == 0)
{
b = 10;
index = 1;
}
if (b > 0)
{
_ = array[index];
}
}
Посмотрите на этот код и скажите — могла ли быть попытка обращения к элементу с индексом -1?
Возможно, в таком примере даже человек не сразу разберётся.
Переменная индекс Нет может быть равно -1, если б > 0 .
После всего б > 0, если только а = 0 , и если а = 0 , затем индекс = 1 .
Надеюсь, вы не запутались :).
Приведенные примеры синтетические, и в реальном коде такое случается не так часто.
Однако пользователи анализаторов иногда сообщают ложноположительные тревоги , причиной чего является связь переменных.
Например, недавно нам написали о проблеме обработки кода следующего содержания: public void Test()
{
var a = GetPotentialNull();
bool z = a != null;
if (z)
{
_ = a.GetHashCode(); // <=
}
}
Увы и ах, ранее анализатор совершенно беззастенчиво врал о возможном разыменовании нулевой ссылки!
Это, конечно, не катастрофа.
Ложные срабатывания, как правило, неизбежны, но анализатор предоставляет различные варианты борьбы с ними.
Самое простое, что вы можете сделать, — это пометить предупреждение как ложное, чтобы оно не раздражало глаз.
Вы можете прочитать об этом больше Здесь .
Однако мы постоянно боремся с ложными срабатываниями, чтобы пользователи не тратили время на их изучение.
"Эта тема, кстати, в статье хорошо освещена" Как и почему статические анализаторы борются с ложными срабатываниями ".
Рекомендую прочитать :).
Вы сражаетесь не в том месте!
Вы можете подумать, что мне не следовало вам всего этого говорить.Ведь я говорю о слабых сторонах статического анализа! Похоже я играю не за ту команду :).
Но на самом деле это совсем не так.
Подобные статьи в первую очередь посвящены развитию анализатора, рассказывая, как нам удалось сделать продукт лучше.
А любое развитие начинается с осознания проблем.
Анализатор работает не идеально? Да.
Иногда не ругается там, где надо, а иногда выдает ложные срабатывания? Бывает. Но мы действительно работаем над этим.
Клиенты пишут нам о проблемах, которые их больше всего волнуют, и мы делаем все, чтобы PVS-Studio стал лучше.
Статьи такого рода позволяют нам рассказать миру о достигнутых успехах :).
Кстати, о них.
PVS-Studio и связанные с ним переменные
Разнообразие возможных переменных связей поражает, и поддержание их — весьма нетривиальная задача.Однако бороться с ложными срабатываниями необходимо, поэтому мы решили постепенно охватить наиболее распространенные способы связи.
Прежде чем начать, небольшое отступление.
Многие из представленных в статье отрывков являются синтетическими.
Они могут показаться странными, и вообще «непонятно, кто это пишет», но поверьте, все примеры основаны на реальном коде.
Примеры максимально просты, но в то же время позволяют воспроизвести то или иное поведение анализатора.
Как разработчики PVS-Studio, мы очень благодарны пользователям, которые пишут нам о проблемах, с которыми они столкнулись (в том числе о ложных срабатываниях).
Но еще больше нам приятно, когда нам присылают такие простые примеры кода, в которых легко воспроизводится некорректное поведение.
Это невероятно ускоряет процесс внесения необходимых исправлений :).
ЭВистический алгоритм
Первым решением, позволившим избавиться от большого количества ложных срабатываний, стал специально подобранный алгоритм.Частично устраняются предупреждения, возникающие из-за неявных ассоциаций различных значений с переменными ссылочных типов.
Изучая ложные срабатывания, мы заметили интересную закономерность.
Если разыменование выполняется в теле условной конструкции, то нулевое состояние соответствующей переменной, скорее всего, связано с выражением в условии.
Другими словами, условные разыменования обычно были безопасными, поскольку соответствующая ссылка неявно проверялась связанной переменной.
Приведем пример: void Test(bool condition)
{
object a;
if (condition)
a = new object();
else
a = null;
.
if (condition)
_ = a.ToString();
}
Поскольку разыменование переменной а производится в теле условной конструкции, анализатор как бы предполагает наличие связи между а и состояние.
Благодаря этому PVS-Studio не выдаст предупреждение.
В этом случае инициирование вызова Нанизывать действительно было бы ложным, поскольку, если бы условие = правда , Что а не равный нулевой.
В таком виде алгоритм отсекал слишком много хороших позитивов, поэтому мы начали думать об улучшениях.
Наилучшие результаты были достигнуты при добавлении дополнительного условия исключения: установка нулевой должен быть в том же методе, в котором выполняется разыменование.
Именно в этих случаях нулевое состояние обычно ассоциируется с условием.
Приведем пример, в котором нулевой можно получить другим методом: bool _flag;
object GetPotentialNull() => _flag ? "not null" : null;
void Test(bool condition)
{
object a = GetPotentialNull();
if (condition)
_ = a.ToString();
}
Переменная а действительно разыменовывается при условии, но между ним и состояние Нет. Эта эвристика позволила «сохранить» много хороших позитивов, хотя и добавила несколько ложных.
Долгое время этот алгоритм был основным инструментом против связанных переменных.
С его помощью теперь можно убрать значительную часть ложных срабатываний в коде реальных проектов.
И все же результаты такого исключения не идеальны: иногда хорошие срабатывания отсекаются, а иногда, наоборот, «пропускаются» ложные срабатывания.
И если потеря пары-тройки хороших позитивов не такая уж и критичная проблема, то с ложными нужно что-то делать.
Не такое уж бессмысленное задание
Вообще клиенты обычно не пишут, что мы должны «поддерживать связанные переменные».Это даже звучит очень абстрактно! Их как пользователей интересует именно качественная продукция PVS-Studio, а вот как и что работает внутри – не так важно.
Поэтому нам пишут о конкретных ложных срабатываниях анализатора.
Мы уже понимаем, в чем именно заключается проблема, и пытаемся ее решить.
И вот в один прекрасный день пользователь пишет нам о предупреждении, выданном по следующему коду: static void Foo()
{
Holder h = new Holder();
Parameter p = h.GetParam();
p.Text = "ABC"; // <=
h.f();
p.Text = "XYZ"; // <=
h.f();
}
В3008 Переменной p.Text присваиваются значения дважды подряд. Возможно, это ошибка.
Проверьте строки: 35, 33. В предупреждении говорится, что первое присвоение бессмысленно — значение «ABC» никак не используется.
Что-то здесь не так, код нужно изучать и исправлять.
Но нет! Присвоение вовсе не бессмысленно.
Но как это может быть? Первая мысль, которая может возникнуть – нужно посмотреть, что это за недвижимость – Текст .
Может быть, присвоение этого свойству на что-то влияет? Ничего подобного: class Parameter
{
internal string Text { get; set; }
}
Нормальное авто свойство.
Присвоение ему значения не приводит к выполнению каких-либо специальных действий.
Соответственно, присвоение ему значения 2 раза подряд. Несколько странно.
Однако тревога по-прежнему ложная.
Чтобы окончательно понять, что здесь происходит, стоит посмотреть на класс Держатель : class Holder
{
private Parameter param;
internal Parameter GetParam()
{
return param;
}
internal Holder()
{
param = new Parameter();
param.Text = "";
}
internal void f()
{
Console.WriteLine("Holder: {0}", param.Text);
}
}
Оказывается, метод ж использует значение свойства параметр.
Текст .
Зная это, вернемся к исходному примеру: static void Foo()
{
Holder h = new Holder();
Parameter p = h.GetParam();
p.Text = "ABC";
h.f();
p.Text = "XYZ";
h.f();
}
Фактически в переменную п пишется ссылка на поле параметр объект час .
При вызове метода ж используется это поле — точнее, используется его свойство Текст .
При первом звонке ж В Текст Написано «ABC», а во втором написано «XYZ».
Таким образом, каждое задание сыграло свою роль и ошибки здесь нет. В данном случае ложное срабатывание вызвано наличием довольно необычной связи между свойством п.
Текст и переменная час .
Вызов ч.
ф() использует значение, записанное в п.
Текст , и диагностике надо это как-то учитывать.
Для решения этой проблемы мы решили доработать одно из исключений, которые уже были в диагностике.
Цель исключения — предотвратить его срабатывание в тех случаях, когда объект использовался между двумя назначениями.
Например: void Test()
{
int a, x;
a = 10;
x = a; // a is used
a = 20;
}
Анализатор не работает с таким кодом, так как переменная а использовался между присвоением ему значений.
В отличие от предыдущего случая, здесь переменная а используется явно, поэтому здесь легко исключить срабатывание.
Но что делать, если присвоенное значение неявно используется при вызове метода? Давайте разберемся: static void Foo()
{
Holder h = new Holder();
Parameter p = h.GetParam();
p.Text = "ABC";
h.f(); // p.Text is used here
p.Text = "XYZ";
h.f(); // and here
}
Чтобы решить проблему, мы решили изменить правило В3008 .
Эта диагностика теперь сохраняет пары потенциально связанных переменных при проверке кода.
Если один из них используется каким-либо образом, то другой также считается использованным.
п считается потенциально связанным с час , так как его значение было получено при вызове h.GetParam() .
В то же время задача ч.
ф() означает не только использование час , но и потенциальное использование связанных с ними п и его свойства.
Вот почему это вызывает «дополнительное задание» п.
Текст больше не генерируется.
Реальный пример общения
Синтетика – это, конечно, хорошо, но кому она интересна? Да, круто, что анализатор стал лучше работать на вымышленных примерах.На самом деле бесполезно, если люди просто не пишут код, который делает улучшение заметным.
Кстати, есть информация об оценке анализаторов на синтетических примерах.
Речь идет о C++, но суть от этого не меняется.
Наш случай совершенно иной.
Во-первых, исправление было сделано в первую очередь по желанию клиента, то есть по крайней мере в коде его проекта мы избавились от ложных срабатываний.
Во-вторых, улучшения производительности анализатора хорошо видны и в других реальных проектах.
Например, давайте посмотрим на код из RavenDB , который мы используем для тестирования PVS-Studio: [Fact]
public void CRUD_Operations_With_Array_In_Object_2()
{
.
var family = new Family() { Names = new[] { "Hibernating Rhinos", "RavenDB" } }; newSession.Store(family, "family/1"); newSession.SaveChanges(); var newFamily = newSession.Load<Family>("family/1"); newFamily.Names = new[] {"Hibernating Rhinos", "RavenDB"}; // <= Assert.Equal(newSession.Advanced.WhatChanged().
Count, 0); newFamily.Names = new[] { "RavenDB", "Hibernating Rhinos" }; // <= Assert.Equal(newSession.Advanced.WhatChanged().
Count, 1); newSession.SaveChanges(); .
}
В3008 Переменной newFamily.Names присваиваются значения два раза подряд. Возможно, это ошибка.
Итак, анализатор сообщил, что в новоеСемья.
Имена значение присваивается дважды без использования первого значения.
И действительно, из кода не видно никакой очевидной пользы.
Но что, если мы присмотримся поближе? Объект класса Семья сохранено в сеансе.
На данный момент он содержит имена «Hibernating Rhinos» и «RavenDB».
Затем загружается тот же объект (или, по крайней мере, объект, содержащий те же значения) из сеанса.
После этого в нем пишут те же имена .
И тут происходит звонок: Assert.Equal(newSession.Advanced.WhatChanged().
Count, 0);
Очевидно, что эта проверка учитывает ранее записанное значение.
Этот тест проверяет отсутствие изменений — ведь имена те же.
Чуть ниже в коде имена меняются местами и снова проводится аналогичная проверка.
Там уже ожидаются изменения.
Связь между вызовами очевидна newSession.Advanced.WhatChanged() И новоеСемейство.
Имена .
Получается, что здесь нет смысла выдавать предупреждения о «ненужных» заданиях.
И угадай что? Теперь PVS-Studio этого делать не будет :).
Поэтому разработчики не будут тратить время на исследование ненужных позитивов.
Мы обнаружили и другие примеры исчезновения ложных срабатываний.
Однако все они сильно напоминают то, что мы обсуждали ранее, поэтому предлагаю перейти к следующему разделу.
Связь через оператора как
Не успели мы порадоваться победе над ложными срабатываниями, сообщавшими о «лишних» заданиях, как другой клиент прислал нам новый пример: void Test(object obj)
{
if (obj != null)
Console.WriteLine("obj is not null");
string str = obj as string;
if (str != null)
Console.WriteLine(obj.GetHashCode()); // <=
}
В3125 Объект obj использовался после проверки его соответствия нулю.
Что ж, давайте разберемся.
Сначала параметр объект проверено на равенство нулевой .
Оказывается, метод предполагает: В объект можно передать нулевую ссылку .
Затем с помощью оператора как преобразование находится в процессе объект печатать Нить , и результат записывается в переменную ул.
.
Самое интересное происходит дальше.
Если ул.
не равный нулевой , то метод вызывается GetHashCode .
Однако это не называется ул.
, и объект ! Оказывается, здесь была проверена не та переменная.
Даже ул.
не равный нулевой , Что объект все еще остается потенциальным нулевой -ценить.
По крайней мере, так может показаться.
Фактически, если ул != ноль , затем объект != ноль .
Почему? Скажем так объект действительно равный нулевой .
Тогда первый чек даст ЛОЖЬ - Ну ладно.
Далее значение для ул.
.
Поскольку переменная объект равно нулевой , затем в ул.
определенно будет нулевой .
Отсюда следует противоположный вывод: если в ул.
Нет нулевой , затем в объект тоже нет нулевой .
Круто, конечно, что мы это поняли, но анализатор тоже надо научить.
В этом нам помогает существующая реализация Data Flow. Для подходящих выражений из анализируемого кода PVS-Studio создает специальные объекты, хранящие информацию о возможных значениях.
Мы называем такие объекты виртуальными ценностями.
Они также содержат вспомогательные данные, широко используемые диагностами.
Например, Data Flow отслеживает, является ли значение переменной:
- результат звонка ФёрстОрдефолт ;
- потенциально зараженный (более здесь );
- результат кастинга через оператор как;
- и так далее.
void Test(object obj)
{
if (obj != null)
Console.WriteLine("obj is not null");
string str = obj as string;
if (str != null)
Console.WriteLine(obj.GetHashCode());
}
К переменной ул.
результат каста написан объект через оператора как .
Data Flow запишет информацию об этом в соответствующее виртуальное значение.
Данная функциональность уже реализована и активно использовалась некоторыми правилами.
Один из них - В3149 .
Во время обработки условий ул != ноль анализатор вычисляет, что если это выражение истинно, то ул.
определенно не равны нулевой .
В этом случае анализатор уже знает, что значение ул.
полученный методом литья объект через оператора как .
Оказывается, смысл объект также вполне справедливо можно считать неравным нулевой .
Реальные примеры соединений через оператор as
Честно говоря, мы сами даже не ожидали такого результата, но исчезла целая куча ложных срабатываний.
Кто бы мог подумать, что такое испытание на нулевой через оператора как Это так часто? Выпуск 1 В качестве первого примера приведу фрагмент кода из проекта КосмическиеИнженеры : void Toolbar_ItemChanged(MyToolbar self, MyToolbar.IndexArgs index)
{
Debug.Assert(self == Toolbar);
var tItem = ToolbarItem.FromItem(self.GetItemAtIndex(index.ItemIndex));
.
}
В3080 Возможно нулевое разыменование возвращаемого значения метода, когда оно передается методу в качестве его первого аргумента.
Итак, в ответе указано, что метод ToolbalItem.FromItem может быть передан нулевой , что приведет к выдаче исключения.
Это так? Прежде всего, вы должны посмотреть на метод Жетитематиндекс : public MyToolbarItem GetItemAtIndex(int index)
{
if (!IsValidIndex(index))
return null;
return this[index];
}
Анализ потока данных позволил анализатору выяснить, что вызов этого метода в некоторых случаях возвращает нулевой .
Но не приведет ли это к проблемам? Теперь перейдем к определению метода Из элемента : public static ToolbarItem FromItem(MyToolbarItem item)
{
var tItem = new ToolbarItem();
tItem.EntityID = 0;
var terminalItem = item as MyToolbarItemTerminalBlock;
if (terminalItem != null)
{
var block = item.GetObjectBuilder() as .
; // <= .
} .
return tItem;
}
Ранее мы видели, что в параметре элемент можно записать нулевой .
Здесь выполняется разыменование и элемент заранее это не проверяется.
Но это проверено терминалитем ! И если терминалитем не равный нулевой , затем элемент определенно не равны нулевой .
Выпуск 2 Мы нашли аналогичный пример в проекте SharpDevelop : DocumentScript GetScript(string fileName)
{
.
var formattingOptions = CSharpFormattingPolicies.Instance .
GetProjectOptions(compilation.GetProject()); .
}
В3080 Возможное нулевое разыменование возвращаемого значения метода compilation.GetProject() в Project.FileName, когда оно передается методу в качестве его первого аргумента.
Итак, анализатор предупредил о возможном разыменовании нулевой ссылки внутри метода.
GetProjectOptions .
Причиной этого является передача компиляция.
GetProject() в качестве первого аргумента.
Давайте разберемся.
Межпроцедурный анализ показал, что GetProject иногда это действительно возвращается нулевой .
Как насчет GetProjectOptions ? Давайте взглянем: public CSharpFormattingPolicy GetProjectOptions(IProject project)
{
if (!initialized)
return GlobalOptions;
var csproject = project as CSharpProject;
if (csproject != null) {
string key = project.FileName; // <=
.
}
return SolutionOptions ?? GlobalOptions;
}
Да, это относится к свойству первого аргумента.
Однако только в том случае, если оно не равно нулевой ! Он просто не проверяет себя проект , а результат его приведения через как .
Выпуск 3 Для кода проекта было выдано еще одно недостающее ложное срабатывание.
ILSpy : protected override Expression DoResolve (ResolveContext ec)
{
var res = expr.Resolve(ec);
var constant = res as Constant;
if (constant != null && constant.IsLiteral)
{
return Constant.CreateConstantFromValue(res.Type, // <=
constant.GetValue(),
expr.Location);
}
return res;
}
В3080 Возможное нулевое разыменование.
Рассмотрите возможность проверки 'res'.
рез получает свое значение от вызова выражение.
Resolve(ec) .
В некоторых случаях он действительно возвращает нулевой .
Но в момент доступа к собственности Тип переменная больше не точно равна нулевой .
Как и раньше, проверка осуществляется неявно, и если константа!= ноль , затем рез Такой же.
Было много других ложных срабатываний, которые "ушли" благодаря поддержке связи через оператора как .
Но все они так или иначе напоминают те, которые мы здесь уже приводили.
Если вы хотите сами проверить, как PVS-Studio обрабатывает случаи такого типа, то вы можете сделать это бесплатно, скачав анализатор с сайта связь .
Веселиться!
Типичные связанные переменные
Ранее мы рассмотрели пару типов подключений, с которыми встречаемся не очень часто.Конечно, проведя тесты, мы увидели, что новые правки действительно дали ощутимые результаты.
Однако гораздо чаще мы встречали случаи связей между переменными логического и ссылочного типов.
Ранее я приводил пример, демонстрирующий эту связь: public void Test()
{
var a = GetPotentialNull();
bool flag = a != null;
if (flag)
{
_ = a.GetHashCode(); // <=
}
}
В3080 Возможное нулевое разыменование.
Рассмотрите возможность проверки «a».
Если флаг = правда , то переменная а не может быть равным нулевой .
Таким образом, неявная проверка защищает от проблем.
Чтобы научить анализатор учитывать такие связи, мы снова решили усовершенствовать наш Data Flow. Однако этот случай был несколько сложнее.
В отличие от случая с оператором как , здесь нужно было добавить новый тип информации о переменной.
В частности, данные о связи с другой переменной.
Обработка объявления флаг , анализатор вычисляет возможные значения переменных из выражения в следующих случаях:
- если выражение (и, следовательно, флаг ) равно истинный ;
- если выражение равно ЛОЖЬ .
- Если флаг == правда , Что а != ноль ;
- Если флаг == ложь , Что
-
В Пещерах Такого Не Было.
19 Oct, 24 -
Поведенческие Технологии В Сети Рлэ.
19 Oct, 24 -
Любители Продукции Apple, Откликнитесь!
19 Oct, 24 -
Перестаньте Избегать Регулярных Выражений
19 Oct, 24