﻿# Ошибки и подозрительные места в исходниках \.NET 8

Каждый год выходит новая версия \.NET\. Это событие не только предоставляет нам возможность познакомиться с последними улучшениями в самом \.NET и нововведениями в языке, но и даёт повод исследовать исходный код \.NET\. Нужно воспользоваться этим шансом\!

![1095_NET8_Errors_ru/image1.png](https://import.viva64.com/docx/blog/1095_NET8_Errors_ru/image1.png)

Кстати говоря, у нас уже есть несколько статей, посвящённых последним обновлениям в мире \.NET и C\#\. Если вас интересует, что в этот раз добавили Microsoft, рекомендую вам заглянуть в следующие материалы:

* [Что нового в \.NET 8?](https://pvs-studio.ru/ru/blog/posts/csharp/1080/)
* [Обзор нововведений в C\# 12](https://pvs-studio.ru/ru/blog/posts/csharp/1074/)

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

Кроме того, в последнем релизе PVS\-Studio 7\.28 уже реализована поддержка анализа проектов, использующих \.NET 8\. Для проведения анализа исходников использовался релизный код \.NET 8, который доступен на GitHub по [ссылке](https://github.com/dotnet/runtime/tree/v8.0.0)\.

Перед тем, как мы приступим к изучению обнаруженных ошибок в \.NET 8, хочу рассказать небольшую историю\. 

Как известно, \.NET огромен, и это может создавать проблемы\. В исходниках есть скрипт, который позволяет сгенерировать решение для \.NET библиотек\. Это решение я проанализировал с помощью консольной утилиты PVS\-Studio\. Отчёт же я принялся изучать в IDE, в которой я работаю — Visual Studio 2022, но возникла проблема\. При попытках навигации по коду в Visual Studio 2022 происходило нечто непредвиденное: либо происходила перезагрузка IDE, либо она просто завершала свою работу\. Причём такое поведение повторяется не только при навигации по коду с помощью плагина PVS\-Studio, но и при обычном переключении между файлами, использовании 'Go To Definition' и т\. д\.

Это усложнило работу, но выход нашёлся быстро\.

Не так давно у нас появилась поддержка анализа \.NET проектов в VS Code\. Про это есть отдельная статья: "[Использование расширения VS Code "PVS\-Studio" для эффективной борьбы с ошибками в C\# коде](https://pvs-studio.ru/ru/blog/posts/csharp/1079/)"\. Учитывая, что VS Code представляет собой легковесный редактор кода, подобных трудностей, с которыми мы столкнулись в Visual Studio 2022, там не возникло\.

Вот так выглядит окно PVS\-Studio в Visual Studio Code:

![1095_NET8_Errors_ru/image2.png](https://import.viva64.com/docx/blog/1095_NET8_Errors_ru/image2.png)

\.NET — мощная платформа, которая имеет высокие стандарты для кода, пишется настоящими профессионалами и хорошо тестируется\. Однако даже в таком крутом проекте PVS\-Studio способен найти ошибки\. 

А теперь давайте перейдём к рассмотрению обнаруженных ошибок\.

**Фрагмент кода 1**

```cpp
private static bool IsRoamingSetting(SettingsProperty setting)
{
  List<KeyValuePair<int, ServiceCallSite>> callSitesByIndex = new();
  ....
  SettingsManageabilityAttribute manageAttr = ....;
  return    manageAttr != null 
         && ((manageAttr.Manageability & SettingsManageability.Roaming) ==
             SettingsManageability.Roaming);
}
```

Предупреждение PVS\-Studio: [V3181](https://pvs-studio.ru/ru/docs/warnings/v3181/) The result of '&' operator is '0' because the value of 'SettingsManageability\.Roaming' is '0'\. LocalFileSettingsProvider\.cs 411

В данном случае значение константы перечисления _SettingsManageability\.Roaming_ равно 0\. Поскольку результат побитового "И" с операндом 0 всегда равен 0, получается, что 0 сравнивается с 0\. Выходит, что результатом выражения _\(\(manageAttr\.Manageability & SettingsManageability\.Roaming\) \=\= SettingsManageability\.Roaming_ всегда является _true_\.

Разработчикам стоит обратить внимание на этот код\.

**Фрагмент кода 2**

```cpp
internal DataView(....)
{
  ....
  DataCommonEventSource.Log.Trace("<ds.DataView.DataView|API> %d#, table=%d, 
                                   RowState=%d{ds.DataViewRowState}\n",
                ObjectID, (table != null) ? table.ObjectID : 0, (int)RowState);
  ....
}
```

Предупреждение PVS\-Studio: [V3025](https://pvs-studio.ru/ru/docs/warnings/v3025/) The 1st argument '"<ds\.DataView\.DataView\|API\> %d\#, table\=%d, RowState\=%d\{ds\.DataViewRowState\}\\n"' is used as incorrect format string inside method\. A different number of format items is expected while calling 'Trace' function\. Arguments not used: 1st, 2nd, 3rd\. DataView\.cs 166, DataCommonEventSource\.cs 45

Анализатор сообщает о некорректной строке формата в первом аргументе метода _Trace_\. Посмотрим на этот метод:

```cpp
internal void Trace<T0, T1, T2>(string format, T0 arg0, T1 arg1, T2 arg2)
{
  if (!Log.IsEnabled()) return;
  Trace(string.Format(format, arg0, arg1, arg2));
}
```

Действительно, первый аргумент используется в качестве строки формата\. В эту строку подставляются аргументы\. Вот только аргументы должны подставляться в плейсхолдеры вида \{0\}, \{1\} и т\. д\. В данной строке подобные плейсхолдеры отсутствуют\. В итоге использования такой строки формата будет выброшено исключение типа _System\.FormatException_ о некорректном формате\.

Возможно, нужно использовать какой\-то другой метод логирования\. Если пройтись по другим местам использования метода _Trace_, то там всё используется корректно, и строки формата содержат маркеры:

![1095_NET8_Errors_ru/image3.png](https://import.viva64.com/docx/blog/1095_NET8_Errors_ru/image3.png)

**Фрагмент кода 3**

```cpp
public static SqlDecimal operator /(SqlDecimal x, SqlDecimal y)
{
  ....
  bScaleD = x._bScale;
  bPrecD = x._bPrec;
  ResScale = Math.Max(x._bScale + y._bPrec + 1, s_cNumeDivScaleMin);
  ResInteger = x._bPrec - x._bScale + y._bScale;
  ResPrec = ResScale + x._bPrec + y._bPrec + 1;               // <=
  MinScale = Math.Min(ResScale, s_cNumeDivScaleMin);

  ResInteger = Math.Min(ResInteger, s_NUMERIC_MAX_PRECISION);
  ResPrec = ResInteger + ResScale;                            // <=
  ....
}
```

Предупреждение PVS\-Studio: [V3008](https://pvs-studio.ru/ru/docs/warnings/v3008/) The 'ResPrec' variable is assigned values twice successively\. Perhaps this is a mistake\. Check lines: 1689, 1685\. SQLDecimal\.cs 1689

В данном фрагменте видно, что происходит двойное присваивание в переменную _ResPrec_\. 

Поскольку между этими двумя операциями _ResPrec_ не используется, это свидетельствует об ошибке\.

Здесь два варианта:

* Одно из присваиваний является лишним — ничего страшного, просто лишняя операция, хотя это и нехорошо;
* Между двумя присваиваниями переменная _ResPrec_ должна использоваться — вот это уже будет неприятной ошибкой\.

**Фрагмент кода 4**

```cpp
public override void MoveToAttribute(int i)
{
  ....
  _currentAttrIndex = i;
  if (i < _coreReaderAttributeCount)
  {
    ....
    _validationState = ValidatingReaderState.OnAttribute;
  }
  else
  {
    ....
    _validationState = ValidatingReaderState.OnDefaultAttribute;
  }

  if (_validationState == ValidatingReaderState.OnReadBinaryContent)
  {
    Debug.Assert(_readBinaryHelper != null);
    _readBinaryHelper.Finish();
    _validationState = _savedState;
  }
}
```

Предупреждение PVS\-Studio: [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) Expression '\_validationState \=\= ValidatingReaderState\.OnReadBinaryContent' is always false\. XsdValidatingReader\.cs 1302

PVS\-Studio обнаружил, что последнее условие _if \(\_validationState \=\= ValidatingReaderState\.OnReadBinaryContent\)_ всегда будет ложным\. Давайте разбираться почему\.

Взглянем на первый оператор _if_\. В нём_ _полю _\_validationState_ присваивается: 

* в then ветви —_ ValidatingReaderState\.OnAttribute_
* в else ветви — _ValidatingReaderState\.OnDefaultAttribute_

Поэтому значение поля не может быть равно _ValidatingReaderState\.OnReadBinaryContent_, и код внутри _if_ не выполняется\.

**Фрагмент кода 5**

```cpp
private static string GetTypeNameDebug(TypeDesc type)
{
  string result;
  TypeDesc typeDefinition = type.GetTypeDefinition();
  if (type != typeDefinition)
  {
    result = GetTypeNameDebug(typeDefinition) + "<";
    for (int i = 0; i < type.Instantiation.Length; i++)
      result += (i == 0 ? "" : ",") + GetTypeNameDebug(type.Instantiation[0]);
    return result + ">";
  }
  else
  {
    ....
  }
  ....
}
```

Предупреждение PVS\-Studio: [V3102](https://pvs-studio.ru/ru/docs/warnings/v3102/) Suspicious access to element of 'type\.Instantiation' object by a constant index inside a loop\. TypeLoaderEnvironment\.GVMResolution\.cs 32

Предположу, что в данном фрагменте кода из информации о типе формируется запись следующего вида: _ConsoleApp1\.Program\.MyClass<string, int, double\>_\. Вот только в цикле обращаются к объекту _type\.Instantiation_ по константному индексу, равному 0\. Не исключено, что работает всё как надо, но выглядит очень странно\. Ожидаешь увидеть _GetTypeNameDebug\(type\.Instantiation\[i\]\)_\.

И да, я сразу пошёл и проверил, в дебаггере Visual Studio 2022 всё отображается корректно, но не исключено, что где\-то можно встретить отображение типа с ошибкой :\)\.

**Фрагмент кода 6**

```cpp
Instruction[]? GetArgumentsOnStack (MethodDefinition method)
{
  int length = method.GetMetadataParametersCount ();
  Debug.Assert (length != 0);
  if (stack_instr?.Count < length)
    return null;

  var result = new Instruction[length];
  while (length != 0)
    result[--length] = stack_instr!.Pop ();    // <=

  return result;
}
```

Предупреждение PVS\-Studio: [V3125](https://pvs-studio.ru/ru/docs/warnings/v3125/) The 'stack\_instr\!' object was used after it was verified against null\. Check lines: 1918, 1913\. UnreachableBlocksOptimizer\.cs 1918

Разработчик использовал оператор '?\.', подразумевая, что поле _stack\_instr_ может быть _null_\. И вроде бы всё хорошо, есть проверка, но не тут\-то было\. В указанной строчке возможно разыменование нулевой ссылки\. Скорее всего, разработчик подумал, что выражение _stack\_instr?\.Count < length_ при _stack\_instr_ равным _null_ вернёт _true_, и произойдёт выход из метода, но нет — результатом будет _false_\.

Более того, разработчик подавил сообщение компилятора о возможном разыменовании _null_ ссылки с помощью '\!', т\.к\. подумал, что статический анализ компилятора просто не справился и не понял проверки\.

А как вы относитесь к nullable контексту? Если интересно наше мнение, или если вы ещё не знакомы с данным механизмом, то предлагаю почитать наши статьи:

* [Помоги компилятору, и он поможет тебе\. Тонкости работы с nullable reference типами в C\#](https://pvs-studio.ru/ru/blog/posts/csharp/1017/)
* [Nullable Reference не защищают, и вот доказательства](https://pvs-studio.ru/ru/blog/posts/csharp/0764/)
* [Nullable Reference типы в C\# 8\.0 и статический анализ](https://pvs-studio.ru/ru/blog/posts/csharp/0631/)

**Фрагмент кода 7**

```cpp
private HierarchyFlags GetFlags (TypeDefinition resolvedType)
{
  if (_cache.TryGetValue (resolvedType, out var flags))
  {
    return flags;
  }

  if (   resolvedType.Name == "IReflect"                // <=
      && resolvedType.Namespace == "System.Reflection") 
  {
    flags |= HierarchyFlags.IsSystemReflectionIReflect;
  }
  ....
  if (resolvedType != null)                             // <=
    _cache.Add (resolvedType, flags);

  return flags;
}
```

Предупреждение PVS\-Studio: [V3095](https://pvs-studio.ru/ru/docs/warnings/v3095/) The 'resolvedType' object was used before it was verified against null\. Check lines: 34, 55\. TypeHierarchyCache\.cs 34

Параметр _resolvedType_ сначала используют, но перед добавлением в кэш проверяют на _null_\. Странно как\-то выходит\. Анализатор указал на _resolvedType\.Name_, но программа упадёт даже раньше\. Метод _TryGetValue_ выбросит исключение, если первый аргумент _resolvedType_ будет _null_\.

**Фрагмент кода 8**

```cpp
public static bool IsTypeOf<T> (this TypeReference tr)
{
  var type = typeof (T);
  return tr.Name == type.Name && tr.Namespace == tr.Namespace;
}
```

Предупреждение PVS\-Studio: [V3001](https://pvs-studio.ru/ru/docs/warnings/v3001/) There are identical sub\-expressions 'tr\.Namespace' to the left and to the right of the '\=\=' operator\. TypeReferenceExtensions\.cs 365

Анализатор выявил, что в данном коде сравниваются два одинаковых подвыражения\. Простая, но обидная ошибка\. _tr\.Namespace_ сравнивается с _tr\.Namespace_, а должен с _type\.Namespace_\.

**Фрагмент кода 9**

```cpp
public void WriteTo(TextWriter writer, int methodRva, bool dumpRva)
{
  ....
  switch (Flags & CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_KIND_MASK)
  {
    case CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_NONE:
      writer.Write($" CATCH: {0}", ClassName ?? "null");
      break;

    case CorExceptionFlag.COR_ILEXCEPTION_CLAUSE_FILTER:
      writer.Write($" FILTER (RVA {0:X4})",
                   ClassTokenOrFilterOffset + methodRva);
      break;
    ....
  }
  ....
}
```

Предупреждение PVS\-Studio: [V3025](https://pvs-studio.ru/ru/docs/warnings/v3025/) Incorrect format\. A different number of format items is expected while calling 'Write' function\. Arguments not used: ClassName ?? "null"\. EHInfo\.cs 135

Ещё одна ошибка со строкой формата, но в этот раз для класса _TextWriter_\. Разработчик использовал символ интерполяции строк '$'\. В строку просто подставится число 0, и строка формата станет равна " CATCH: 0"\. В итоге текст, который хотели подставить вместо плейсхолдера \{0\}, не используется\. Такая же ошибка и в следующем _case_\.

**Фрагмент кода 10**

```cpp
public TType ParseType()
{
  CorElementType corElemType = ReadElementType();
  switch (corElemType)
  {
    ....
    case CorElementType.ELEMENT_TYPE_GENERICINST:
    {
      TType genericType = ParseType();
      uint typeArgCount = ReadUInt();
      var outerDecoder = new R2RSignatureDecoder<....>(_provider,
                                                       Context,
                                                       _outerReader, // <=
                                                       _image,
                                                       _offset,
                                                       _outerReader, // <=
                                                       _contextReader);
  }
}
```

Предупреждение PVS\-Studio: [V3038](https://pvs-studio.ru/ru/docs/warnings/v3038/) The argument was passed to constructor several times\. It is possible that other argument should be passed instead\. ReadyToRunSignature\.cs 707

Аргумент _\_outerReader_ передаётся в конструктор два раза\. Если взглянуть на объявление конструктора, то можно увидеть, что конструктор имеет параметр с именем _metadataReader_:

```cpp
public R2RSignatureDecoder(IR2RSignatureTypeProvider<....> provider,
                           TGenericContext context,
                           MetadataReader metadataReader,  // <=
                           byte[] signature,
                           int offset,
                           MetadataReader outerReader,     // <=
                           ReadyToRunReader contextReader,
                           bool skipOverrideMetadataReader = false)
{
  ....
}
```

В момент вызова конструктора доступно поле _\_metadataReader_\. Возможно, в качестве третьего аргумента стоит использовать именно его\.

**Фрагмент кода 11 — бонус**

```cpp
protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(....)
{
  bool requiresAlign8 
    =    !largestAlignmentRequired.IsIndeterminate 
      && context.Target.PointerSize == 4
      && context.Target.GetObjectAlignment(....).AsInt > 4 
      && context.Target.PointerSize == 4;
}
```

Предупреждение PVS\-Studio: [V3001](https://pvs-studio.ru/ru/docs/warnings/v3001/) There are identical sub\-expressions 'context\.Target\.PointerSize \=\= 4' to the left and to the right of the '&&' operator\. MetadataFieldLayoutAlgorithm\.cs 648

В выражении два раза проверяется _context\.Target\.PointerSize \=\= 4_\. В экземпляром методе _GetObjectAlignment_ изменение _context\.Target\.PointerSize_ не происходит\. Возможно, что здесь должно проверяться что\-то ещё, а может это просто лишняя проверка\.

Как я уже писал ранее, \.NET имеет код высокого качества\. И тем не менее, я не перестаю удивляться некоторым ошибкам, которые находятся в проектах подобной величины\. Отлаженный процесс разработки, разработчики высшего класса, но всё равно встречаются ошибки и странности в коде\. Безусловно, это нормально, идеального кода не существует, но к нему можно и нужно стремиться\.

Предлагаю и вам проверить свой проект на наличие странностей и ошибок\. Попробовать анализатор можно по [ссылке](https://pvs-studio.ru/ru/pvs-studio/try-free/)\. [Пишите нам](https://pvs-studio.ru/ru/about-feedback/), если будут вопросы — мы оперативно решаем все возникшие проблемы :\)\.