Это небольшой рассказ о том, как PVS-Studio помог нам найти ошибку в исходном коде библиотеки, используемой в PVS-Studio. И это была не теоретическая ошибка, а реальная - ошибка возникала на практике при использовании библиотеки в анализаторе.
В PVS-Studio_Cmd (как и в некоторых других утилитах) мы используем специальную библиотеку для разбора аргументов командной строки - CommandLine.
Сегодня я поддержал новый режим в PVS-Studio_Cmd и так получилось, что мне пришлось использовать эту библиотеку для разбора аргументов командной строки. Во время написания кода я также отлаживаю его, потому что мне приходится работать с незнакомыми API.
Итак, код написан, скомпилирован, выполнен и…
Выполнение кода происходит внутри библиотеки, где возникает исключение типа NullReferenceException. Со стороны не все так ясно - я не передаю в метод нулевых ссылок.
Конечно, я смотрю комментарии к вызываемому методу. Вряд ли они описывают условия возникновения исключения типа NullReferenceException (как мне кажется, обычно исключения этого типа не предусмотрены).
В комментариях к методу нет информации о NullReferenceException (что, однако, ожидаемо).
Чтобы узнать, что именно вызывает исключение (и где оно возникает), я решил загрузить исходный код проекта, собрать его и добавить в анализатор ссылку на отладочную версию библиотеки. Исходный код проекта доступен на GitHub. Нам нужна версия библиотеки 1.9.71. Это тот, который сейчас используется в анализаторе.
Скачиваю соответствующую версию исходного кода, собираю библиотеку, добавляю в анализатор ссылку на отладочную библиотеку, выполняю код и вижу:
Итак, место возникновения исключения ясно - helpInfo имеет значение null, которое вызывает исключение типа NullReferenceException при доступе к Left.
Я начал думать об этом. В последнее время 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, но и в коде используемых библиотек.
Напоследок предлагаю вам скачать анализатор и попробовать проверить свой проект - а вдруг там вы тоже найдете что-то интересное?