В блоге PVS-Studio вы можете найти не одну статью с результатами проверки исходного кода различных компиляторов.
С другой стороны, PVS-Studio, судя по всему, несколько пренебрегает другим классом программ — классом декомпиляторов.
Чтобы сделать мир более гармоничным, был проверен исходный C#-код программы ILSpy, принадлежащей лагерю декомпиляторов.
Посмотрим, что интересного смог найти PVS-Studio.
Введение
Наверное, каждому программисту хотя бы раз в жизни приходилось пользоваться декомпилятором.У каждого из нас могут быть разные цели: узнать реализацию какого-то метода, подтвердить или опровергнуть свои подозрения об ошибке в используемой нами библиотеке или просто из любопытства посмотреть исходный код программы, которую мы используем.
заинтересованы, близки к этому.
В мире .
NET при упоминании слова «декомпилятор» обычно на ум приходят dotPeek или ILSpy. .
NET Reflector теперь меньше запоминается.
Помню, как, когда я впервые узнал о таком классе утилит и декомпилировал чужую библиотеку, в голове пронеслась мысль о шпионаже.
И не у меня одного были такие мысли — не зря декомпилятор ILSpy получил свое название.
Итак, в этой статье я описал интересные и подозрительные с моей точки зрения места, обнаруженные с помощью PVS-Studio в исходном коде проекта ILSpy. Мне хотелось, так сказать, увидеть, что скрывает наш шпион, и при необходимости «прикрыть» это статическим анализатором.
Честно говоря, статья про ILSpy вышла несколько случайно.
Среди пользователей нашего анализатора довольно много студий, занимающихся разработкой игр.
Это одна из причин, почему мы в компании стараемся сделать наш инструмент максимально полезным и удобным для разработчиков игр, в том числе использующих Unity и Unreal Engine. Я знаю клиентов, которые используют PVS-Studio в связке с Unreal Engine, но гораздо реже слышу о Unity-разработчиках, практикующих использование нашего анализатора.
В связи с этим мне бы хотелось популяризировать анализатор среди сообщества Unity. Одним из способов его популяризации могла бы стать статья о тестировании игры с открытым исходным кодом, разработанной с использованием этого движка.
Но тут у меня возникла проблема - я не смог найти такую игру (может быть вы, читатели, сможете любезно предлагайте идеи для таких проверок ?).
Обстоятельства поиска игры с открытым исходным кодом были немного странными, и один сайт ILSpy входил в список самых популярных проектов для Unity (для меня остается загадкой, как и почему он попал в этот список).
Кстати, ILSpy входит в пул проектов, на которых мы внутри компании регулярно тестируем наш C#-анализатор.
Странно, что у нас до сих пор не было статьи об этом проекте.
Что ж, поскольку нам не удалось найти проект Unity для анализа, давайте посмотрим на ILSpy, который привлек наше внимание.
Вот что написано в описании проекта на GitHub : ILSpy — это браузер и декомпилятор сборок .
NET с открытым исходным кодом.
Я не нашел информации о том, что при разработке проекта используется какой-либо статический анализатор.
Это, наверное, даже интереснее — первым будет PVS-Studio. Перейдем непосредственно к результатам анализа.
Замена без замены
В3038 Аргумент «»»» был передан методу «Заменить» несколько раз.Возможно, вместо этого следует передать другой аргумент. ICSharpCode.Decompiler ReflectionDisassembler.cs 772
Похоже, автор хотел заменить все вхождения символа одинарной кавычки строкой, состоящей из двух символов: символа обратной косой черты и символа одинарной кавычки.private static void WriteSimpleValue(ITextOutput output, object value, string typeName) { switch (typeName) { case "string": output.Write( "'" + DisassemblerHelpers .
EscapeString(value.ToString()) .
Replace("'", "\'") // <= + "'"); break; case "type": .
} .
}
По невнимательности произошла осечка - символ "'" меняется сам на себя, что является бессмысленной операцией.
Нет никакой разницы между присвоением значения «'» и «\'» строке — в любом случае строка будет инициализирована одинарной кавычкой.
Если мы хотим, чтобы значение «\'» было включено в строку, необходимо экранировать обратную косую черту или использовать символ «@»: «\\'» или @"\'».
То есть вызов метода Заменять следует переписать следующим образом: Replace("'", @"\'")
Правда и только правда
Предупреждение 1 В3022 Выражение 'negatedOp == BinaryOperatorType.Any' всегда истинно.
ICSharpCode.Декомпилятор CSharpUtil.cs static Expression InvertConditionInternal(Expression condition)
{
var bOp = (BinaryOperatorExpression)condition;
if ( (bOp.Operator == BinaryOperatorType.ConditionalAnd)
|| (bOp.Operator == BinaryOperatorType.ConditionalOr))
{
.
} else if ( (bOp.Operator == BinaryOperatorType.Equality) || (bOp.Operator == BinaryOperatorType.InEquality) || (bOp.Operator == BinaryOperatorType.GreaterThan) || (bOp.Operator == BinaryOperatorType.GreaterThanOrEqual) || (bOp.Operator == BinaryOperatorType.LessThan) || (bOp.Operator == BinaryOperatorType.LessThanOrEqual)) { .
} else { var negatedOp = NegateRelationalOperator(bOp.Operator); if (negatedOp == BinaryOperatorType.Any) // <= return new UnaryOperatorExpression(.
);
bOp = (BinaryOperatorExpression)bOp.Clone();
bOp.Operator = negatedOp;
return bOp;
}
}
Анализатор предупреждает, что значение переменной отрицательныйOp всегда равен значению Любой из списка БинарныйОператорТип .
Чтобы убедиться в этом, давайте посмотрим на код метода Отрицательный реляционный оператор , из которого эта переменная получает свое значение.
public static BinaryOperatorType NegateRelationalOperator(BinaryOperatorType op)
{
switch (op)
{
case BinaryOperatorType.GreaterThan:
return BinaryOperatorType.LessThanOrEqual;
case BinaryOperatorType.GreaterThanOrEqual:
return BinaryOperatorType.LessThan;
case BinaryOperatorType.Equality:
return BinaryOperatorType.InEquality;
case BinaryOperatorType.InEquality:
return BinaryOperatorType.Equality;
case BinaryOperatorType.LessThan:
return BinaryOperatorType.GreaterThanOrEqual;
case BinaryOperatorType.LessThanOrEqual:
return BinaryOperatorType.GreaterThan;
case BinaryOperatorType.ConditionalOr:
return BinaryOperatorType.ConditionalAnd;
case BinaryOperatorType.ConditionalAnd:
return BinaryOperatorType.ConditionalOr;
}
return BinaryOperatorType.Any;
}
Если к моменту вызова метода Отрицательный реляционный оператор переменная bOp.Оператор имело значение, которое не соответствовало ни одной метке случай , то метод вернет значение BinaryOperatorType.Любой .
Видно, что вызов метода Отрицательный реляционный оператор происходит только в том случае, если условия в родительском операторе если и оператор если еще были рассчитаны как ЛОЖЬ .
А если очень внимательно, то становится заметно, что условия в операторах если И если еще закрыть все отметки случай из метода Отрицательный реляционный оператор .
Таким образом, к моменту вызова метода Отрицательный реляционный оператор переменная bOp.Оператор не соответствует ни одному ярлыку случай , и этот метод в этом случае всегда будет возвращать значение BinaryOperatorType.Любой .
Так что получается, что negatedOp == BinaryOperatorType.Any всегда оценивается как истинный , а в следующей строке возвращается значение из метода.
Кроме того, мы получаем недостижимый код: bOp = (BinaryOperatorExpression)bOp.Clone();
bOp.Operator = negatedOp;
return bOp;
Кстати, анализатор любезно выдал за это предупреждение: В3142 Обнаружен недоступный код. Вполне возможно, что ошибка присутствует. ICSharpCode.Декомпилятор CSharpUtil.cs 81 Предупреждение 2 В3022 Выражение «pt != null» всегда истинно.
ICSharpCode.Decompiler FunctionPointerType.cs 168 public override IType VisitChildren(TypeVisitor visitor)
{
.
IType[] pt = (r != ReturnType) ? new IType[ParameterTypes.Length] : null; .
if (pt == null)
return this;
else
return new FunctionPointerType(
module, CallingConvention, CustomCallingConventions,
r, ReturnIsRefReadOnly,
pt != null ? pt.ToImmutableArray() : ParameterTypes, // <=
ParameterReferenceKinds);
}
Здесь все очевидно - ветка еще выполняется только в том случае, если переменная пт не равный нулевой .
Зачем тогда нужно писать тернарный оператор с проверкой переменных? пт о неравенстве нулевой, Я не понимаю.
Возможно, в прошлом не было ветвления если еще и первый оператор возвращаться .
Тогда такая проверка имела бы смысл, но сейчас лишний тернарный оператор все же стоит убрать: public override IType VisitChildren(TypeVisitor visitor)
{
.
IType[] pt = (r != ReturnType) ? new IType[ParameterTypes.Length] : null; .
if (pt == null)
return this;
else
return new FunctionPointerType(
module, CallingConvention, CustomCallingConventions,
r, ReturnIsRefReadOnly,
pt.ToImmutableArray(), ParameterReferenceKinds);
}
Предупреждение 3 В3022 Выражение «settings.LoadInMemory» всегда истинно.
ICSharpCode.Decompiler CSharpDecompiler.cs 394 static PEFile LoadPEFile(string fileName, DecompilerSettings settings)
{
settings.LoadInMemory = true;
return new PEFile(
fileName,
new FileStream(fileName, FileMode.Open, FileAccess.Read),
streamOptions: settings.LoadInMemory ? // <=
PEStreamOptions.PrefetchEntireImage : PEStreamOptions.Default,
metadataOptions: settings.ApplyWindowsRuntimeProjections ?
MetadataReaderOptions.ApplyWindowsRuntimeProjections :
MetadataReaderOptions.None
);
}
Аналогично предыдущей операции анализатора получаем совершенно ненужный тернарный оператор.
Свойство настройки.
LoadInMemory , протестированному в тернарном операторе выше, присваивается значение истинный , который не меняется до самого тернарного оператора.
Для полноты картины приведем код для получения и установки самого свойства: public bool LoadInMemory {
get { return loadInMemory; }
set {
if (loadInMemory != value)
{
loadInMemory = value;
OnPropertyChanged();
}
}
}
Переписанный метод без тернарного оператора представлять, думаю, не стоит — здесь все достаточно просто.
Предупреждение 4 В3022 Выражение «та» всегда не равно нулю.
Оператор '??' является чрезмерным.
ICSharpCode.Decompiler ParameterizedType.cs 354 public IType VisitChildren(TypeVisitor visitor)
{
.
if (ta == null)
return this;
else
return new ParameterizedType(g, ta ?? typeArguments); // <=
}
Здесь мы уже сталкиваемся с ненужным нулевой слияние оператор.
При ударе веткой еще переменная та неравенство всегда имеет значение нулевой .
Как следствие, использование оператора ?? здесь слишком много всего.
Всего было найдено 31 предупреждение с номером В3022 .
Ты здесь странный
Предупреждение 1 В3025 Неправильный формат. При вызове функции «Формат» ожидается другое количество элементов формата.Аргументы не используются: Конец.
ICSharpCode.Decompiler Interval.cs 269 public override string ToString()
{
if (End == long.MinValue)
{
if (Start == long.MinValue)
return string.Format("[long.MinValue.long.MaxValue]", End); // <=
else
return string.Format("[{0}.
long.MaxValue]", Start); } else if (Start == long.MinValue) { return string.Format("[long.MinValue.{0})", End); } else { return string.Format("[{0}.
{1})", Start, End);
}
}
При вызове самого первого метода строка.
Формат Строка формата не соответствует фактическим аргументам, переданным методу.
Значение переменной Конец , переданный в качестве аргумента, не будет подставлен в строку формата, поскольку в нем отсутствует элемент формата {0}.
По логике этого метода это все же не ошибка, первое утверждение возвращаться вернет именно ту строку, которую задумали авторы кода.
Это, конечно, не меняет того факта, что происходит бесполезный вызов метода строка.
Формат с неиспользуемым аргументом.
Хорошо бы это исправить, чтобы не вводить в заблуждение человека, который будет читать этот метод. Предупреждение 2 В3025 Неправильный формат. При вызове функции AppendFormat ожидается другое количество элементов формата.
Не используемые аргументы: угол.
ILSpy.BamlDecompiler XamlPathDeserializer.cs 177 public static string Deserialize(BinaryReader reader)
{
.
var sb = new StringBuilder(); .
sb.AppendFormat(CultureInfo.InvariantCulture, "A{0} {2:R} {2} {3} {4}", size, angle, largeArc ? '1' : '0', sweepDirection ? '1' : '0', pt1); .
}
В данном случае переменная была опущена.
угол .
Несмотря на то, что он был передан методу АппендФормат , из-за отсутствия элемента формата {1} в строке формата и двукратного использования {2} эта переменная остается неиспользованной.
Скорее всего авторы намеревались записать форматную строку так: "А{0} {1:R} {2} {3} {4}" .
Двойные стандарты
Предупреждение 1 В3095 Объект roslynProject использовался до того, как его соответствие нулю было проверено.
Проверить строки: 96, 97. ILSpy.AddIn OpenILSpyCommand.cs 96 protected Dictionary<string, detectedreference=""> GetReferences(.
) { .
var roslynProject = owner.Workspace .
CurrentSolution .
GetProject(projectReference.ProjectId); var project = FindProject(owner.DTE.Solution .
Projects.OfType<envdte.project>(), roslynProject.FilePath); // <= if (roslynProject != null && project != null) // <= .
}
Сначала мы получаем доступ к собственности Путь к файлу объект РослинПроект не опасаясь, что в переменной РослинПроект можно записать нулевой , и буквально строкой ниже мы проверяем равенство этой переменной по нулевой .
Этот код выглядит небезопасно и чреват возникновением исключения типа NullReferenceException .
Чтобы исправить эту ситуацию, следует использовать свойство Путь к файлу , используя нулевой условный оператор, и в методе НайтиПроект обеспечить возможность получения потенциальных нулевой в качестве последнего параметра.
Предупреждение 2 В3095 Объект listBox использовался до того, как было проверено его соответствие нулю.
Проверьте строки: 46, 52. ILSpy FlagsFilterControl.xaml.cs 46 public override void OnApplyTemplate()
{
base.OnApplyTemplate();
listBox = Template.FindName("ListBox", this) as ListBox;
listBox.ItemsSource = FlagGroup.GetFlags(.
); // <= var filter = Filter; if (filter == null || filter.Mask == -1) { listBox?.
SelectAll(); // <=
}
}
Ситуация аналогична предыдущему примеру.
Сначала мы получаем доступ к собственности ПредметыИсточник без какой-либо проверки того, что находится в переменной списокBox можно записать нулевой , а несколькими строками ниже мы уже видим использование нулевого условного оператора с переменной списокBox .
Более того, переменная списокBox между этими двумя вызовами поле и метод не получили нового значения.
Всего анализатор нашел 10 предупреждений с номером В3095 .
Вот список этих предупреждений:
- V3095 Объект «pV» использовался до того, как было проверено его соответствие нулю.
Проверить строки: 761, 765. ICSharpCode.Decompiler TypeInference.cs 761
- V3095 Объект «pU» использовался до того, как было проверено его соответствие нулю.
Проверить строки: 882, 886. ICSharpCode.Decompiler TypeInference.cs 882
- V3095 Объект FinalStore использовался до того, как было проверено его соответствие нулю.
Проверьте строки: 261, 262. ICSharpCode.Decompiler TransformArrayInitializers.cs 261
- V3095 Объект «definitionDeclaringType» использовался до того, как было проверено его соответствие нулю.
Проверьте строки: 93, 104. ICSharpCode.Decompiler SpecializedMember.cs 93
- V3095 Объект TypeNamespace использовался до того, как было проверено его соответствие нулю.
Проверьте строки: 84, 88. ILSpy.BamlDecompiler XamlType.cs 84
- V3095 Объект «property.Getter» использовался до того, как было проверено его соответствие нулю.
Проверить строки: 1676, 1684. ICSharpCode.Decompiler CSharpDecompiler.cs 1676
- V3095 Объект «ev.AddAccessor» использовался до того, как он был проверен на нулевое значение.
Проверить строки: 1709, 1717. ICSharpCode.Decompiler CSharpDecompiler.cs 1709
- V3095 Объект targetType использовался до того, как было проверено его соответствие нулю.
Проверить строки: 1614, 1657. ICSharpCode.Decompiler CallBuilder.cs 1614
Зайдя на сайт PVS-Studio, вы можете как скачать сам анализатор, так и получить пробную лицензию.
Все идет к одному
Предупреждение 1 В3139 Две или более ветвей дела выполняют одни и те же действия.
ILSpy Images.cs 251 protected override ImageSource GetBaseImage(MemberIcon icon)
{
ImageSource baseImage;
switch (icon)
{
case MemberIcon.Field:
baseImage = Images.Field;
break;
case MemberIcon.FieldReadOnly:
baseImage = Images.FieldReadOnly;
break;
case MemberIcon.Literal:
baseImage = Images.Literal; // <=
break;
case MemberIcon.EnumValue:
baseImage = Images.Literal; // <=
break;
case MemberIcon.Property:
baseImage = Images.Property;
break;
case MemberIcon.Indexer:
baseImage = Images.Indexer;
break;
case MemberIcon.Method:
baseImage = Images.Method;
break;
case MemberIcon.Constructor:
baseImage = Images.Constructor;
break;
case MemberIcon.VirtualMethod:
baseImage = Images.VirtualMethod;
break;
case MemberIcon.Operator:
baseImage = Images.Operator;
break;
case MemberIcon.ExtensionMethod:
baseImage = Images.ExtensionMethod;
break;
case MemberIcon.PInvokeMethod:
baseImage = Images.PInvokeMethod;
break;
case MemberIcon.Event:
baseImage = Images.Event;
break;
default:
throw new ArgumentOutOfRangeException(nameof(icon),
$"MemberIcon.{icon} is not supported!");
}
return baseImage;
}
На мой взгляд, это явная ошибка.
В случае, если переменная икона равно MemberIcon.EnumValue , то переменная базовое изображение в теме случай должен получить значение Изображения.
EnumValue .
Это хороший пример ошибки, которую легко заметить статическим анализатором и легко пропустить человеком при беглом просмотре кода.
Предупреждение 2 В3139 Две или более ветвей дела выполняют одни и те же действия.
ICSharpCode.Decompiler CSharpConversions.cs 829 bool ImplicitConstantExpressionConversion(ResolveResult rr, IType toType)
{
.
switch (toTypeCode) { case TypeCode.SByte: return val >= SByte.MinValue && val <= SByte.MaxValue; case TypeCode.Byte: return val >= Byte.MinValue && val <= Byte.MaxValue; case TypeCode.Int16: return val >= Int16.MinValue && val <= Int16.MaxValue; case TypeCode.UInt16: return val >= UInt16.MinValue && val <= UInt16.MaxValue; case TypeCode.UInt32: return val >= 0; // <= case TypeCode.UInt64: return val >= 0; // <= } .
}
Не скажу, что анализатор нашел здесь очевидную ошибку, но предупреждение определенно имеет смысл.
Если этикетки случай по стоимости Код Типа.
UInt32 И Код Типа.
UInt64 выполнить тот же набор действий, почему бы не написать код компактнее: bool ImplicitConstantExpressionConversion(ResolveResult rr, IType toType)
{
switch (toTypeCode)
{
.
case TypeCode.UInt32: case TypeCode.UInt64: return val >= 0; } .
}
Анализатор выдал еще 2 предупреждения с номером В3139 :
- V3139 Две или более ветвей дела выполняют одни и те же действия.
ICSharpCode.Decompiler EscapeInvalidIdentifiers.cs 85
- V3139 Две или более ветвей дела выполняют одни и те же действия.
ICSharpCode.Decompiler TransformExpressionTrees.cs 370
Безопасность прежде всего
В3083 Возможен небезопасный вызов события NullReferenceException. Рассмотрите возможность присвоения события локальной переменной перед ее вызовом.
ILSpy MainWindow.xaml.cs 787class ResXResourceWriter: IDisposable void assemblyList_Assemblies_CollectionChanged(.
) { .
if (CurrentAssemblyListChanged != null)
CurrentAssemblyListChanged(this, e); // <=
}
Этот способ вызова событий довольно распространен, но тот факт, что мы видим этот паттерн во многих проектах, не является оправданием его использования.
Конечно, это не критическая ошибка, но, как сказано в тексте предупреждения анализатора, этот вызов небезопасен, и возникает исключение типа NullReferenceException .
Если между проверкой CurrentAssemblyListChanged на нулевой а вызвав само событие, все обработчики от него отписались (например, в другом потоке выполнения), тогда будет выброшено исключение NullReferenceException .
Лучше сразу написать безопасный код, например такой: void assemblyList_Assemblies_CollectionChanged(.
) { .
CurrentAssemblyListChanged?.
Invoke(this, e);
}
PVS-Studio нашел еще 8 подобных случаев, и все их можно исправить тем же способом, что и представленный выше.
Уверенная неуверенность
В3146 Возможное нулевое разыменование.«FirstOrDefault» может возвращать нулевое значение по умолчанию.
ILSpy.BamlDecompiler BamlResourceEntryNode.cs 76 bool LoadBaml(AvalonEditTextOutput output, CancellationToken cancellationToken)
{
var asm = this.Ancestors().
OfType<assemblytreenode>() .
FirstOrDefault().
LoadedAssembly; // <= .
return true;
}
Из коллекции, возвращаемой методом Тип , вызвав метод ФёрстОрдефолт хочу получить первый встретившийся элемент типа СборкаДеревоУзел .
Все мы знаем, что если в коллекции не найден ни один элемент, удовлетворяющий предикату поиска, или если сама коллекция пуста, то метод ФёрстОрдефолт вернет значение по умолчанию - нулевой в нашем случае.
Дальнейший доступ к объекту Загруженная сборка по нулевой ссылке приведет к исключению типа NullReferenceException .
Соответственно, чтобы избежать такой ситуации, следует использовать нулевой условный оператор: bool LoadBaml(AvalonEditTextOutput output, CancellationToken cancellationToken)
{
var asm = this.Ancestors().
OfType<assemblytreenode>() .
FirstOrDefault()?.
LoadedAssembly; // <= .
return true;
}
Можно предположить, что разработчик уверен, что именно в этом месте метод ФёрстОрдефолт никогда не вернется нулевой .
Если возникла такая ситуация, то лучше воспользоваться методом Первый вместо ФёрстОрдефолт , так как подчеркивает нашу уверенность в том, что мы всегда получим нужную вещь из коллекции.
При этом если элемент не найден в коллекции, то мы получим исключение вида ИнвалидОператионИсключение (но нет NullReferenceException как и в случае с использованием ФёрстОрдефолт с последующим доступом к свойству через нулевую ссылку) с четким сообщением: «Последовательность не содержит элементов».
Небезопасное сканирование
В3105 Переменная 'm' использовалась после того, как она была присвоена с помощью условного оператора с нулевым значением.
Возможно исключение NullReferenceException. ILSpy MethodVirtualUsedByAnalyzer.cs 137 static bool ScanMethodBody(IMethod analyzedMethod,
IMethod method, MethodBodyBlock methodBody)
{
.
var mainModule = (MetadataModule)method.ParentModule; .
switch (member.Kind) { case HandleKind.MethodDefinition: case HandleKind.MethodSpecification: case HandleKind.MemberReference: var m = (mainModule.ResolveEntity(member, genericContext) as IMember) ?.
MemberDefinition; if ( m.MetadataToken == analyzedMethod.MetadataToken // <= && m.ParentModule.PEFile == analyzedMethod.ParentModule.PEFile) // <= { return true; } break; } .
}
При инициализации переменной м Использовался нулевой условный оператор, поэтому предполагается, что м потенциально может иметь значение нулевой .
Интересно, что сразу на следующей строке мы обращаемся к свойствам переменной без использования нулевого условного оператора.
м , что чревато возникновением исключения типа NullReferenceException .
Как и в некоторых других примерах из этой статьи, исправим ситуацию с помощью условного оператора null: static bool ScanMethodBody(IMethod analyzedMethod,
IMethod method, MethodBodyBlock methodBody)
{
.
var mainModule = (MetadataModule)method.ParentModule; .
switch (member.Kind) { case HandleKind.MethodDefinition: case HandleKind.MethodSpecification: case HandleKind.MemberReference: var m = (mainModule.ResolveEntity(member, genericContext) as IMember) ?.
MemberDefinition; if ( m?.
MetadataToken == analyzedMethod.MetadataToken && m?.
ParentModule.PEFile == analyzedMethod.ParentModule.PEFile) { return true; } break; } .
}
Старые знакомые
В3070 Неинициализированная переменная «схема» используется при инициализации переменной «ResourceSchema».
ICSharpCode.Decompiler ResXResourceWriter.cs 63 class ResXResourceWriter : IDisposable
{
.
public static readonly string ResourceSchema = schema; .
static string schema = .
; .
}
Изначально я не планировал делать такое предупреждение.
Дело в том, что абсолютно идентичная ошибка уже была обнаружена пять лет назад в результате проверки проекта.
Мононуклеоз , но после небольшого обсуждения с коллегой пришли к выводу, что упомянуть в статье об этом все же стоит. Как описано в статье про проверку Mono, в момент инициализации статического поля Ресурссхема еще одно статическое поле схема , поле схема сам еще не инициализирован и имеет значение по умолчанию — нулевой .
Файл ResXResourceWriter.cs, в котором была обнаружена ошибка, был любезно позаимствован с соблюдением авторских прав из проекта Mono. В файл были добавлены некоторые уникальные функции проекта ILSpy. Именно так ошибки из одного проекта распространяются по сети и мигрируют из одного проекта в другой.
Кстати, ошибка в первоисточнике до сих пор не исправлена.
Заключение
Подводя итоги анализа, можно сказать, что были обнаружены не только фрагменты исходного кода, которые просто хотелось бы переписать в целях рефакторинга, но и реальные ошибки, где авторы кода явно ожидали иного результата (например, тот же вызов метода Заменять с теми же аргументами).Регулярное использование статического анализа позволяет быстро находить и корректировать такие подозрительные места.
Исправить ошибку всегда проще и дешевле на этапе написания/тестирования кода, чем на производстве, когда вам уже сообщает об ошибке пользователь, а не статический анализатор.
Спасибо за прочтение.
На заметку для тех, кто хочет самостоятельно протестировать ILSpy.
Анализируя проект ILSpy, мы обнаружили несколько проблем, связанных с самим анализатором — да, такое случается.Мы исправили проблемы, но в релиз версии 7.11 изменения не вошли, начиная со следующей версии они будут доступны.
Также из-за того, что проект ILSpy собран немного нестандартно, потребовались некоторые дополнительные настройки анализатора.
Итак, если вы хотите проверить ILSpy самостоятельно — напишите нам .
Мы выпустим бета-версию анализатора и расскажем, как настроить сканирование.
Если вы хотите поделиться этой статьей с англоязычной аудиторией, воспользуйтесь ссылкой для перевода: Илья Гайнулин.
Шпион под прикрытием: PVS-Studio проверит исходный код ILSpy .
Теги: #программирование #C++ #.
NET #pvs-studio #pvs #статический анализ кода
-
Интересная Особенность Oracle Sql
19 Oct, 24 -
Предложение По Добавлению Привязки Данных
19 Oct, 24