Это небольшой рассказ о том, как PVS-Studio помог нам найти ошибку в исходном коде библиотеки, используемой в PVS-Studio. И это была не теоретическая ошибка, а реальная — ошибка проявлялась на практике при использовании библиотеки в анализаторе.

В PVS-Studio_Cmd (как и в некоторых других утилитах) мы используем специальную библиотеку для разбора аргументов командной строки — CommandLine.

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

Итак, код написан, скомпилирован, выполнен и…

Выполнение кода происходит внутри библиотеки, где возникает исключение типа NullReferenceException. Со стороны не все так понятно — я не передаю в метод никаких нулевых ссылок.

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

В комментариях к методу нет информации о NullReferenceException (что, впрочем, ожидаемо).

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

Скачиваю соответствующую версию исходников, собираю библиотеку, добавляю в анализатор ссылку на отладочную библиотеку, выполняю код и вижу:

Итак, место возникновения исключения понятно — helpInfo имеет значение null, что вызывает исключение типа NullReferenceException при обращении к Левоесвойство.

Я начал думать об этом. За последнее время PVS-Studio для C# был хорошо улучшен в различных аспектах, в том числе в поиске разыменования потенциально нулевых ссылок. В частности, межпроцедурный анализ был улучшен рядом способов. Поэтому мне сразу же стало интересно проверить исходный код, чтобы понять, сможет ли PVS-Studio найти обсуждаемую ошибку.

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

Предупреждение PVS-Studio: V3080 возможно нулевое разыменование внутри метода в helpInfo.Left. Рассмотрите возможность проверки второго аргумента: helpInfo. Parser.cs 405

Да, это оно! Это именно то, что нам нужно. Рассмотрим более подробно исходный код.

private bool DoParseArgumentsVerbs(
  string[] args, object options, ref object verbInstance)
{
  var verbs 
    = ReflectionHelper.RetrievePropertyList<VerbOptionAttribute>(options);
  var helpInfo 
    = ReflectionHelper.RetrieveMethod<HelpVerbOptionAttribute>(options);
  if (args.Length == 0)
  {
    if (helpInfo != null || _settings.HelpWriter != null)
    {
      DisplayHelpVerbText(options, helpInfo, null); // <=
    }
    return false;
  }
  ....
}

Анализатор выдает предупреждение о вызове метода DisplayHelpVerbText и предупреждает о втором аргументе — helpInfo. Обратите внимание, что этот метод находится в ветви then оператора if. Условное выражение составлено таким образом, что ветвь then может выполняться при следующих значениях переменных:

  • helpInfo == null;
  • _settings.HelpWriter != null;

Давайте посмотрим на тело метода DisplayHelpVerbText:

private void DisplayHelpVerbText(
  object options, Pair<MethodInfo, 
  HelpVerbOptionAttribute> helpInfo, string verb)
{
  string helpText;
  if (verb == null)
  {
    HelpVerbOptionAttribute.InvokeMethod(options, helpInfo, null, out helpText);
  }
  else
  {
    HelpVerbOptionAttribute.InvokeMethod(options, helpInfo, verb, out helpText);
  }
  if (_settings.HelpWriter != null)
  {
    _settings.HelpWriter.Write(helpText);
  }
}

Поскольку verb == null (см. вызов метода), нас интересует then-ветвь оператора if. Хотя с веткой else ситуация похожая, давайте рассмотрим then-ветку, потому что в нашем конкретном случае выполнение шло через нее. Помните, что helpInfo может быть null.

Теперь давайте посмотрим на тело метода HelpVerbOptionAttribute.InvokeMethod. Собственно, вы уже видели это на скриншоте выше:

internal static void InvokeMethod(
    object target,
    Pair<MethodInfo, HelpVerbOptionAttribute> helpInfo,
    string verb,
    out string text)
{
  text = null;
  var method = helpInfo.Left;
  if (!CheckMethodSignature(method))
  {
    throw new MemberAccessException(
      SR.MemberAccessException_BadSignatureForHelpVerbOptionAttribute
        .FormatInvariant(method.Name));
  }
  text = (string)method.Invoke(target, new object[] { verb });
}

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

Заключение

Приятно, что с помощью PVS-Studio нам удалось найти ошибку в исходном коде библиотеки, используемой в PVS-Studio. Думаю, это своего рода ответ на вопрос «Находит ли PVS-Studio ошибки в исходном коде PVS-Studio?». :) Анализатор умеет находить ошибки не только в коде PVS-Studio, но и в коде используемых библиотек.

Напоследок предлагаю вам скачать анализатор и попробовать проверить свой проект — вдруг и там вы найдете что-нибудь интересное?