﻿# Какие ошибки прячутся в коде Infer\.NET?

Публикация корпорацией Microsoft исходников своих проектов является вполне хорошим поводом для их проверки\. Этот раз исключением не стал, и сегодня мы посмотрим на подозрительные места, найденные в коде Infer\.NET\. Долой аннотацию – ближе к делу\!

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

## Немного о проекте и анализаторе

Infer\.NET – система машинного обучения, разрабатываемая специалистами из Microsoft\. Исходный код проекта недавно стал доступен на [GitHub](https://github.com/dotnet/infer), что и послужило поводом к его проверке\. Более подробно о проекте можно почитать, например, [здесь](https://dotnet.github.io/infer/)\.

Проверялся проект с помощью статического анализатора PVS\-Studio версии 6\.26\. Напомню, что PVS\-Studio занимается поиском ошибок в коде на C\\C\+\+\\C\# \(а скоро и на Java\) под Windows, Linux, macOS\. C\# код пока анализируем только под Windows\. Анализатор можно [скачать и попробовать](https://pvs-studio.ru/ru/pvs-studio/download/) на своём проекте\. 

Сама проверка прошла предельно просто и без проблем\. Предварительно я выгрузил проект с GitHub, восстановил требуемые пакеты \(зависимости\) и убедился, что проект успешно собирается\. Это требуется для того, чтобы анализатору была доступна вся необходимая информация для проведения полноценного анализа\. После сборки в пару кликов запустил анализ solution через плагин PVS\-Studio для Visual Studio\.

Кстати, это не первый проект от Microsoft, проверенный с помощью PVS\-Studio – были и другие: [Roslyn](https://pvs-studio.ru/ru/blog/posts/csharp/0363/), [MSBuild](https://pvs-studio.ru/ru/blog/posts/csharp/0424/), [PowerShell](https://pvs-studio.ru/ru/blog/posts/csharp/0447/), [CoreFX](https://pvs-studio.ru/ru/blog/posts/csharp/0365/) и [прочие](https://pvs-studio.ru/ru/blog/inspections/)\.

**Примечание**\. Если вам или вашим знакомым интересен анализ Java кода \-  можете написать нам в [поддержку](https://pvs-studio.ru/ru/about-feedback/), выбрав пункт "Хочу анализатор для Java"\. Публичной beta\-версии анализатора пока нет, но скоро должна появиться\. Где\-то в секретной лаборатории \(через стенку\) ребята активно над ней трудятся\.

Но хватит отвлечённых разговоров – давайте посмотрим на проблемы в коде\.

## Это баг или фича?

Предлагаю попробовать найти ошибку самостоятельно – вполне решаемая задача\. Никаких подколов в духе того, что было в статье "[Toп 10 ошибок в C\+\+ проектах за 2017 год](https://pvs-studio.ru/ru/blog/posts/cpp/0565/)", честно\. Так что не спешите читать предупреждение анализатора, представленное после фрагмента кода\.

```cpp
private void MergeParallelTransitions()
{
  ....
  if (   transition1.DestinationStateIndex == 
         transition2.DestinationStateIndex 
      && transition1.Group == 
         transition2.Group) 
  {
    if (transition1.IsEpsilon && transition2.IsEpsilon)
    {
      ....
    }
    else if (!transition1.IsEpsilon && !transition2.IsEpsilon) 
    {
      ....
      if (double.IsInfinity(transition1.Weight.Value) &&    
          double.IsInfinity(transition1.Weight.Value))
      {
        newElementDistribution.SetToSum(
          1.0, transition1.ElementDistribution,
          1.0, transition2.ElementDistribution);
      }
      else
      { 
        newElementDistribution.SetToSum(
          transition1.Weight.Value, transition1.ElementDistribution, 
          transition2.Weight.Value, transition2.ElementDistribution);
      }
  ....
}
```

**Предупреждение PVS\-Studio**: [V3001](https://pvs-studio.ru/ru/docs/warnings/v3001/) There are identical sub\-expressions 'double\.IsInfinity\(transition1\.Weight\.Value\)' to the left and to the right of the '&&' operator\. Runtime Automaton\.Simplification\.cs 479

Как видно из фрагмента кода, в методе идёт работа с парой переменных – _transition1_ и _transition2_\. Использование схожих имён иногда вполне оправданно, но стоит помнить, что в таком случае возрастает вероятность случайно ошибиться где\-нибудь с именем\.

Так и произошло при проверке чисел на бесконечность \(_double\.IsInfinity_\)\.  Из\-за ошибки 2 раза проверили значение одной и той же переменной \- _transition1\.Weight\.Value_\. Проверяемым значением во втором подвыражении должна была стать переменная _transition2\.Weight\.Value_\.

Ещё один схожий подозрительный код\.

```cpp
internal MethodBase ToMethodInternal(IMethodReference imr)
{
  ....
  bf |=   BindingFlags.Public 
        | BindingFlags.NonPublic 
        | BindingFlags.Public
        | BindingFlags.Instance;
  ....
}
```

**Предупреждение PVS\-Studio**: [V3001](https://pvs-studio.ru/ru/docs/warnings/v3001/) There are identical sub\-expressions 'BindingFlags\.Public' to the left and to the right of the '\|' operator\. Compiler CodeBuilder\.cs 194

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

Кстати, в исходниках этот код записан в одну строку\. Мне кажется, что если он отформатирован в табличном стиле \(как здесь\), проблему обнаружить проще\.

Идём дальше\. Привожу тело метода целиком и вновь предлагаю вам найти ошибку \(а может – ошибки\) самостоятельно\.

```cpp
private void ForEachPrefix(IExpression expr,
                           Action<IExpression> action)
{
  // This method must be kept consistent with GetTargets.
  if (expr is IArrayIndexerExpression)
    ForEachPrefix(((IArrayIndexerExpression)expr).Target,
                  action);
  else if (expr is IAddressOutExpression)
    ForEachPrefix(((IAddressOutExpression)expr).Expression,
                  action);
  else if (expr is IPropertyReferenceExpression)
    ForEachPrefix(((IPropertyReferenceExpression)expr).Target,  
                  action);
  else if (expr is IFieldReferenceExpression)
  {
    IExpression target = ((IFieldReferenceExpression)expr).Target;
    if (!(target is IThisReferenceExpression))
      ForEachPrefix(target, action);
  }
  else if (expr is ICastExpression)
    ForEachPrefix(((ICastExpression)expr).Expression,
                  action);
  else if (expr is IPropertyIndexerExpression)
    ForEachPrefix(((IPropertyIndexerExpression)expr).Target, 
                  action);
  else if (expr is IEventReferenceExpression)
    ForEachPrefix(((IEventReferenceExpression)expr).Target,
                  action);
  else if (expr is IUnaryExpression)
    ForEachPrefix(((IUnaryExpression)expr).Expression,
                  action);
  else if (expr is IAddressReferenceExpression)
    ForEachPrefix(((IAddressReferenceExpression)expr).Expression, 
                  action);
  else if (expr is IMethodInvokeExpression)
    ForEachPrefix(((IMethodInvokeExpression)expr).Method,
                  action);
  else if (expr is IMethodReferenceExpression)
    ForEachPrefix(((IMethodReferenceExpression)expr).Target,
                  action);
  else if (expr is IUnaryExpression)
    ForEachPrefix(((IUnaryExpression)expr).Expression,
                  action);
  else if (expr is IAddressReferenceExpression)
    ForEachPrefix(((IAddressReferenceExpression)expr).Expression, 
                  action);
  else if (expr is IDelegateInvokeExpression)
    ForEachPrefix(((IDelegateInvokeExpression)expr).Target,
                  action);
  action(expr);
}
```

Нашли? Сверяемся\!

**Предупреждения PVS\-Studio**:

* [V3003](https://pvs-studio.ru/ru/docs/warnings/v3003/) The use of 'if \(A\) \{\.\.\.\} else if \(A\) \{\.\.\.\}' pattern was detected\. There is a probability of logical error presence\. Check lines: 1719, 1727\. Compiler CodeRecognizer\.cs 1719
* [V3003](https://pvs-studio.ru/ru/docs/warnings/v3003/) The use of 'if \(A\) \{\.\.\.\} else if \(A\) \{\.\.\.\}' pattern was detected\. There is a probability of logical error presence\. Check lines: 1721, 1729\. Compiler CodeRecognizer\.cs 1721

Немного упростим код, чтобы проблемы стали более очевидны\.

```cpp
private void ForEachPrefix(IExpression expr,
                           Action<IExpression> action)
{
  if (....)
  ....
  else if (expr is IUnaryExpression)
    ForEachPrefix(((IUnaryExpression)expr).Expression,
                  action);
  else if (expr is IAddressReferenceExpression)
    ForEachPrefix(((IAddressReferenceExpression)expr).Expression, 
                  action);
  ....
  else if (expr is IUnaryExpression)
    ForEachPrefix(((IUnaryExpression)expr).Expression,
                  action);
  else if (expr is IAddressReferenceExpression)
    ForEachPrefix(((IAddressReferenceExpression)expr).Expression, 
                   action)
  ....
}
```

Дублируются условные выражения и _then_\-ветви нескольких операторов _if_\. Возможно, этот код писался методом copy\-paste, из\-за чего и возникла проблема\. Сейчас получается, что _then_\-ветви дубликатов никогда не выполнятся, так как:

* если условное выражение истинно, выполняется тело первого оператора _if_ из соответствующей пары;
* если условное выражение ложно в первом случае, оно будет ложно и во втором\.

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

Продолжаем\.

```cpp
public int Compare(Pair<int, int> x, Pair<int, int> y)
{
  if (x.First < y.First)
  {
    if (x.Second >= y.Second)
    {
      // y strictly contains x
      return 1;
    }
    else
    {
      // No containment - order by left bound
      return 1;
    }
  }
  else if (x.First > y.First)
  {
    if (x.Second <= y.Second)
    {
      // x strictly contains y
      return -1;
    }
    else
    {
      // No containment - order by left bound
      return -1;
    }
  }
  ....
}
```

**Предупреждения PVS\-Studio**:

* [V3004](https://pvs-studio.ru/ru/docs/warnings/v3004/) The 'then' statement is equivalent to the 'else' statement\. Runtime RegexpTreeBuilder\.cs 1080
* [V3004](https://pvs-studio.ru/ru/docs/warnings/v3004/) The 'then' statement is equivalent to the 'else' statement\. Runtime RegexpTreeBuilder\.cs 1093

Код выглядит крайне подозрительно, так как содержит два условных оператора с одинаковыми телами _then_ и _else_\-ветвей\. Вероятно, в обоих случаях стоит возвращать разные значения\. Или же, если это задуманное поведение, будет полезно убрать избыточные условные операторы\.

Встречались интересные циклы\. Пример ниже:

```cpp
private static Set<StochasticityPattern> 
IntersectPatterns(IEnumerable<StochasticityPattern> patterns)
{
    Set<StochasticityPattern> result 
      = new Set<StochasticityPattern>();
    result.AddRange(patterns);
    bool changed;
    do
    {
        int count = result.Count;
        AddIntersections(result);
        changed = (result.Count != count);
        break;
    } while (changed);
    return result;
}
```

**Предупреждение PVS\-Studio**: [V3020](https://pvs-studio.ru/ru/docs/warnings/v3020/) An unconditional 'break' within a loop\. Compiler DefaultFactorManager\.cs 474

Из\-за безусловного оператора _break_ выполняется ровно одна итерация цикла, а управляющая переменная _changed_ даже не используется\. В общем, код выглядит странно и подозрительно\.

Такой же метод \(точная копия\) встретился и в другом классе\. Соответствующее предупреждение анализатора: [V3020](https://pvs-studio.ru/ru/docs/warnings/v3020/) An unconditional 'break' within a loop\. Visualizers\.Windows FactorManagerView\.cs 350 

Кстати, встретился метод с безусловным оператором _continue_ в цикле \(его нашёл анализатор этой же диагностикой\), но над ним стоял комментарий, подтверждающий, что это специальное временное решение:

```cpp
// TEMPORARY
continue;
```

Напоминаю, что около безусловного оператора _break_ таких комментариев не было\.

Идём дальше\.

```cpp
internal static DependencyInformation GetDependencyInfo(....)
{
  ....
  IExpression resultIndex = null;
  ....
  if (resultIndex != null)
  {
    if (parameter.IsDefined(
          typeof(SkipIfMatchingIndexIsUniformAttribute), false))
    {
      if (resultIndex == null)
        throw new InferCompilerException(
                     parameter.Name 
                   + " has SkipIfMatchingIndexIsUniformAttribute but " 
                   + StringUtil.MethodNameToString(method) 
                   + " has no resultIndex parameter");
      ....
     }
     ....
  }
  ....
}
```

**Предупреждение PVS\-Studio**: [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) Expression 'resultIndex \=\= null' is always false\. Compiler FactorManager\.cs 382

Сразу отмечу, что между объявлением и приведённой проверкой значение переменной _resultIndex_ может измениться\. Однако между проверками _resultIndex \!\= null_ и _resultIndex \=\= null_ значение уже поменяться не может\. Следовательно, результатом выражения _resultIndex \=\= null_ всегда будет значение _false_, а значит, исключение никогда сгенерировано не будет\.

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

```cpp
public static Tuple<int, string> ComputeMovieGenre(int offset,
                                                   string feature)
{
  string[] genres = feature.Split('|');
  if (genres.Length < 1 && genres.Length > 3)
  {
    throw 
      new ArgumentException(string.Format(
            "Movies should have between 1 and 3 genres; given {0}.", 
            genres.Length));
  }

  double value = 1.0 / genres.Length;

  var result 
    = new StringBuilder(
            string.Format(
              "{0}:{1}",
              offset + MovieGenreBuckets[genres[0]],
              value));
  for (int i = 1; i < genres.Length; ++i)
  {
    result.Append(
      string.Format(
        "|{0}:{1}", 
        offset + MovieGenreBuckets[genres[i].Trim()],
        value));
  }

  return 
    new Tuple<int, string>(MovieGenreBucketCount, result.ToString());
}
```

Давайте посмотрим, что здесь происходит\. Входная строка парсится по символу '\|'\. Если длина массива не соответствует ожидаемой, нужно сгенерировать исключение\. Секундочку\.\.\. _genres\.Length < 1 && genres\.Length \> 3_ ? Так как нет числа, которое попадало бы сразу в оба требуемых выражением диапазона значений \(_\[int\.MinValue\.\.1\)_ и _\(3\.\.int\.MaxValue\]_\), результатом выражения всегда будет значение _false_\. Следовательно, данная проверка ни от чего не защищает, и ожидаемое исключение сгенерировано не будет\.

Именно об этом и предупреждает анализатор: [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) Expression 'genres\.Length < 1 && genres\.Length \> 3' is always false\. Probably the '\|\|' operator should be used here\. Evaluator Features\.cs 242 

Встретилась подозрительная операция деления\.

```cpp
public static void CreateTrueThetaAndPhi(....)
{
  ....
  double expectedRepeatOfTopicInDoc 
    = averageDocLength / numUniqueTopicsPerDoc;
  ....
  int cnt = Poisson.Sample(expectedRepeatOfTopicInDoc);
  ....
}
```

**Предупреждение PVS\-Studio**: [V3041](https://pvs-studio.ru/ru/docs/warnings/v3041/) The expression was implicitly cast from 'int' type to 'double' type\. Consider utilizing an explicit type cast to avoid the loss of a fractional part\. An example: double A \= \(double\)\(X\) / Y;\. LDA Utilities\.cs 74

Подозрительно здесь вот что: выполняется целочисленное деление \(переменные _averageDocLength_ и _numUniqueTopicsPerDoc _имеют тип _int_\), а результат записывается в переменную типа _double_\. Напрашивается вопрос: а специально ли это сделано, или всё же подразумевалось деление вещественных чисел? Если бы переменная _expectedRepeatOfTopicInDoc_ имела тип _int_, это сняло бы возможные вопросы\.

В других местах метод _Poisson\.Sample_, аргументом которого и выступает подозрительная переменная _expectedRepeatOfTopicInDoc_, используется, например, так, как описано ниже\.

```cpp
int numUniqueWordsPerTopic 
  = Poisson.Sample((double)averageWordsPerTopic);
```

_averageWordsPerTopic_ имеет тип _int_, который уже на месте использования приводится к _double_\.

А вот другое место использования:

```cpp
double expectedRepeatOfWordInTopic 
  = ((double)numDocs) * averageDocLength / numUniqueWordsPerTopic;
....
int cnt = Poisson.Sample(expectedRepeatOfWordInTopic);
```

Обратите внимание, переменные носят такие же имена, что и в исходном примере, только для инициализации _expectedRepeatOfWordInTopic_ используется уже деление вещественных чисел \(за счёт явного приведения _numDocs_ к типу _double_\)\.

В общем, стоит посмотреть исходное место, на которое анализатор выдал предупреждение\.

Но размышления над тем, стоит ли это править, и как, оставим авторам кода \(им же виднее\), а сами пойдём дальше\. К следующему подозрительном делению\.

```cpp
public static NonconjugateGaussian BAverageLogarithm(....)
{
  ....
  double v_opt = 2 / 3 * (Math.Log(mx * mz / Ex2 / 2) - m);
  if (v_opt != v)
  {
    ....
  }
  ....
}
```

**Предупреждение PVS\-Studio**: [V3041](https://pvs-studio.ru/ru/docs/warnings/v3041/) The expression was implicitly cast from 'int' type to 'double' type\. Consider utilizing an explicit type cast to avoid the loss of a fractional part\. An example: double A \= \(double\)\(X\) / Y;\. Runtime ProductExp\.cs 137

Анализатор вновь обнаружил подозрительную операцию целочисленного деления, т\.к\. _2_ и _3_ – целочисленные числовые литералы, а результатом выражения _2 / 3_ будет _0_\. В итоге всё выражение принимает вид:

```cpp
double v_opt = 0 * expr;
```

Согласитесь, немного странно\. Несколько раз я возвращался к данному предупреждению, пытаясь найти какой\-то подвох, и не стремясь добавлять его в статью\. Метод наполнен математикой и разными формулами \(разбирать которые, если честно, не очень\-то хотелось\), мало ли чего тут ожидать\. К тому же, я стараюсь максимально скептически относиться к предупреждениям, которые выписываю в статью, и описываю их только предварительно получше изучив\.

Но потом меня осенило – а зачем вообще нужен множитель в виде _0_, записанный как _2 / 3_? Так что это место, в любом случае, стоит посмотреть\.

```cpp
public static void 
  WriteAttribute(TextWriter writer,
                 string name,
                 object defaultValue, 
                 object value, 
                 Func<object, string> converter = null)
{
  if (   defaultValue == null && value == null 
      || value.Equals(defaultValue))
  {
    return;
  }
  string stringValue = converter == null ? value.ToString() : 
                                           converter(value);
  writer.Write($"{name}=\"{stringValue}\" ");
}
```

**Предупреждение PVS\-Studio**: [V3080](https://pvs-studio.ru/ru/docs/warnings/v3080/) Possible null dereference\. Consider inspecting 'value'\. Compiler WriteHelpers\.cs 78

Вполне справедливое утверждение анализатора на основе условия\. Разыменование нулевой ссылки может произойти в выражении _value\.Equals\(defaultValue\)_, если _value \=\= null_\. Так как это выражение – правый операнд оператора \|\|, для его вычисления левый операнд должен иметь значение _false_, а для этого достаточно, чтобы хотя бы одна из переменных _defaultValue_ \\ _value_ не была равна _null_\. В итоге, если _defaultValue \!\= null_, а _value \=\= null_:

* _defaultValue \=\= null_ \-\> _false_;
* _defaultValue \=\= null && value \=\= null_ \-\> _false_; \(проверка _value_ не произошла\)
* _value\.Equals\(defaultValue\)_ \-\> _NullReferenceException_, так как _value_ – _null_\.

Посмотрим ещё на схожий случай:

```cpp
public FeatureParameterDistribution(
         GaussianMatrix traitFeatureWeightDistribution, 
         GaussianArray biasFeatureWeightDistribution)
{
  Debug.Assert(
    (traitFeatureWeightDistribution == null && 
     biasFeatureWeightDistribution == null)
     ||
     traitFeatureWeightDistribution.All(
       w =>    w != null 
            && w.Count == biasFeatureWeightDistribution.Count),
    "The provided distributions should be valid 
     and consistent in the number of features.");
  ....
}
```

**Предупреждение PVS\-Studio**: [V3080](https://pvs-studio.ru/ru/docs/warnings/v3080/) Possible null dereference\. Consider inspecting 'traitFeatureWeightDistribution'\. Recommender FeatureParameterDistribution\.cs 65

Выкинем лишнее, оставив только логику вычисления булева значения, чтобы было проще разобраться:

```cpp
(traitFeatureWeightDistribution == null && 
 biasFeatureWeightDistribution == null)
||
traitFeatureWeightDistribution.All(
  w =>   w != null 
      && w.Count == biasFeatureWeightDistribution.Count)
```

Опять же, правый операнд оператора \|\| вычисляется только в том случае, если результат вычисления левого имеет значение _false_\. Левый операнд может принять значение _false_, в том числе, когда _traitFeatureWeightDistribution \=\= null_ и _biasFeatureWeightDistribution \!\= null_\. Тогда будет вычисляться правый операнд оператора \|\|, а вызов _traitFeatureWeightDistribution\.All_ приведёт к возникновению _ArgumentNullException_\. 

Ещё интересный фрагмент кода:

```cpp
public static double GetQuantile(double probability,
                                 double[] quantiles)
{
  ....
  int n = quantiles.Length;
  if (quantiles == null)
    throw new ArgumentNullException(nameof(quantiles));
  if (n == 0)
    throw new ArgumentException("quantiles array is empty", 
                                nameof(quantiles));
  ....
}
```

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

Обратите внимание, что сначала происходит обращение к свойству _quantiles\.Length_, а затем _quantiles_ проверяется на равенство _null_\. В итоге, если _quantiles \=\= null_, метод выбросит исключение, только немного не то, и немного не там, где это было необходимо\. Видимо, перепутали строки местами\.

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

\(Для увеличения изображения нажмите на него\)

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

Ладно, ладно, это была шутка \(или вам всё же удалось?\!\)\. Давайте немного упростим задачу:

```cpp
if (sample.Precision < 0)
{
  precisionIsBetween = true;
  lowerBound = -1.0 / v;
  upperBound = -mean.Precision;
}
else if (sample.Precision < -mean.Precision)
{
  precisionIsBetween = true;
  lowerBound = 0;
  upperBound = -mean.Precision;
}
else
{
  // in this case, the precision should NOT be in this interval.
  precisionIsBetween = false;
  lowerBound = -mean.Precision;
  lowerBound = -1.0 / v;
}
```

Стало лучше? Анализатор выдал на данный код следующее предупреждение: [V3008](https://pvs-studio.ru/ru/docs/warnings/v3008/) The 'lowerBound' variable is assigned values twice successively\. Perhaps this is a mistake\. Check lines: 324, 323\. Runtime GaussianOp\.cs 324

И действительно, в последней _else_\-ветви два раза подряд присваивается значение переменной _lowerBound_\. Видимо \(и судя по коду выше\), в одном из присвоений должна участвовать переменная _upperBound_\.

Следуем дальше\.

```cpp
private void WriteAucMatrix(....)
{
  ....
  for (int c = 0; c < classLabelCount; c++)
  {
    int labelWidth = labels[c].Length;

    columnWidths[c + 1] = 
      labelWidth > MaxLabelWidth ? MaxLabelWidth : labelWidth;
    for (int r = 0; r < classLabelCount; r++)
    {
      int countWidth = MaxValueWidth;
      if (countWidth > columnWidths[c + 1])
      {
        columnWidths[c + 1] = countWidth;
      }
    }

  ....
}
```

**Предупреждение PVS\-Studio**: [V3081](https://pvs-studio.ru/ru/docs/warnings/v3081/) The 'r' counter is not used inside a nested loop\. Consider inspecting usage of 'c' counter\. CommandLine ClassifierEvaluationModule\.cs 459

Обратите внимание, что счётчик внутреннего цикла – _r_ – не используется в теле этого цикла\. Из\-за этого выходит, что на протяжении всех итераций внутреннего цикла выполняются одни и те же операции над одними и теми же элементами – ведь в индексах также используется счётчик внешнего цикла \(_c_\), а не внутреннего \(_r_\)\.

Посмотрим, что ещё нашлось интересного\.

```cpp
public RegexpFormattingSettings(
         bool putOptionalInSquareBrackets,
         bool showAnyElementAsQuestionMark,
         bool ignoreElementDistributionDetails,
         int truncationLength,
         bool escapeCharacters,
         bool useLazyQuantifier)
{
  this.PutOptionalInSquareBrackets = putOptionalInSquareBrackets;
  this.ShowAnyElementAsQuestionMark = showAnyElementAsQuestionMark;
  this.IgnoreElementDistributionDetails = 
    ignoreElementDistributionDetails;
  this.TruncationLength = truncationLength;
  this.EscapeCharacters = escapeCharacters;
}
```

**Предупреждение PVS\-Studio**: [V3117](https://pvs-studio.ru/ru/docs/warnings/v3117/) Constructor parameter 'useLazyQuantifier' is not used\. Runtime RegexpFormattingSettings\.cs 38

В конструкторе не используется один параметр – _useLazyQuantifier_\. Особенно подозрительным это выглядит на фоне того, что в классе определено свойство с соответствующим именем и типом – _UseLazyQuantifier_\. Видимо, забыли провести его инициализацию через соответствующий параметр\.

Встретилось несколько потенциально опасных обработчиков событий\. Пример одного из них приведён ниже:

```cpp
public class RecommenderRun
{
  ....
  public event EventHandler Started;
  ....
  public void Execute()
  {
    // Report that the run has been started
    if (this.Started != null)
    {
      this.Started(this, EventArgs.Empty);
    }
      ....
  }
  ....
}
```

**Предупреждение PVS\-Studio**: [V3083](https://pvs-studio.ru/ru/docs/warnings/v3083/) Unsafe invocation of event 'Started', NullReferenceException is possible\. Consider assigning event to a local variable before invoking it\. Evaluator RecommenderRun\.cs 115

Дело в том, что между проверкой на неравенство _null_ и вызовом обработчика может произойти отписка от события, и, если в момент между проверкой на _null_ и вызовом обработчиков у события не окажется подписчиков, будет сгенерировано исключение _NullReferenceException_\. Во избежание таких проблем можно, например, сохранять ссылку на цепочку делегатов в локальную переменную или использовать оператор '?\.' для вызова обработчиков\. 

Помимо приведённого выше фрагмента кода нашлось 35 подобных мест\.

Кстати, ещё встретилось **785** предупреждений [V3024](https://pvs-studio.ru/ru/docs/warnings/v3024/)\. Предупреждение [V3024](https://pvs-studio.ru/ru/docs/warnings/v3024/) выдаётся при сравнении вещественных чисел с использованием операторов '\!\=' или '\=\='\.  Не буду здесь останавливаться на том, почему такие сравнения не всегда корректны – подробнее про это написано в документации, там же есть ссылка на [Stack Overflow](https://stackoverflow.com/questions/1398753/comparing-double-values-in-c-sharp) \(это она же\)\.

Учитывая, что часто встречались формулы и вычисления, эти предупреждения также могут быть важны, хоть и вынесены на 3 уровень \(так как актуальны далеко не во всех проектах\)\.

Если же есть уверенность в том, что эти предупреждения неактуальны – можно убрать их [практически одним щелчком](https://pvs-studio.ru/ru/docs/manual/0013/), сократив общее количество срабатываний анализатора\.

![0590_InferNET_ru/image4.png](https://import.viva64.com/docx/blog/0590_InferNET_ru/image4.png)

## Заключение

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

Разработчикам желаю скорейшего исправления проблемных мест и напоминаю, что делать ошибки – это нормально, все же мы люди\. Для того и нужны дополнительные инструменты вроде статических анализаторов, чтобы находить то, что пропустил человек, верно? Так или иначе – удачи с проектом, и спасибо за труд\!

И помните, что максимальная польза от статического анализатора достигается при его _регулярном использовании_\.

Всех благ\!