Это небольшая история о том, как с помощью 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 Possible null dereference inside method at 'helpInfo.Left'. Consider inspecting the 2nd argument: 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-ветвь может быть исполнена при следующих значениях переменных:
Посмотрим тело метода 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, но и в коде используемых библиотек.
Напоследок предложу скачать анализатор и попробовать проверить свой проект - вдруг и там удастся найти что-нибудь интересное?