{"id":382751,"date":"2024-06-29T04:28:31","date_gmt":"2024-06-29T04:28:31","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=382751"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=382751","title":{"rendered":"<span>A Spy Undercover: PVS-Studio to Check ILSpy Source Code<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>In PVS-Studio, we often check various compilers&#8217; code and post the results in our blog. Decompiler programs, however, seem to be a bit neglected. To restore justice in this world, we analyzed the ILSpy decompiler&#8217;s source code. Let&#8217;s take a look at the peculiar things PVS-Studio found.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b31\/8d0\/0bc\/b318d00bcdfc2091f0ed42c46d95eb7d.png\" width=\"354\" height=\"244\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b31\/8d0\/0bc\/b318d00bcdfc2091f0ed42c46d95eb7d.png\"\/><figcaption><\/figcaption><\/figure>\n<h3>Introduction<\/h3>\n<p>Probably almost every programmer used a decompiler at least once. The reasons could vary: to see how a method is implemented, to check if there is an error inside a library used, or to satisfy curiosity and look up some source code. At the mention of a decompiler, most .NET programmers will think of dotPeek or ILSpy. .NET Reflector is not as popular anymore. I remember when I first learned about these utilities and decompiled someone else&#8217;s library &#8212; a thought of espionage ran through my head. I was obviously not the only one thinking along these lines &#8212; I am sure ILSpy&#8217;s name is not accidental. I was curious what the spy is made of and wanted to reinforce it with a static analyzer. So I used the PVS-Studio analyzer on ILSpy&#8217;s source code and put together an article based on the most interesting and suspicious code fragments I found.<\/p>\n<p>To be honest, this article on ILSpy just sort of happened. Some of our clients are game development studios. This is one of the reasons why we try to make our tool as helpful and handy as possible for game developers, especially for those who employ Unity and Unreal Engine.<\/p>\n<p>While I know many clients who work with Unreal Engine, I don&#8217;t encounter that many Unity developers who use our analyzer. I want to encourage them to try the PVS-Studio analyzer, because I believe the Unity community can benefit from it. A cool way to demonstrate it would be to analyze a Unity-based open-source game and present the results. But the problem is &#8212; I could not find such a game! So please <a href=\"https:\/\/github.com\/viva64\/pvs-studio-check-list\">let me know of any ideas<\/a> you have for such games I could analyze with PVS-Studio. When I did try to look for a Unity-based open-source game, my search yielded unexpected results. On one <a href=\"https:\/\/awesomeopensource.com\/projects\/unity\">website<\/a>, I found a list of Unity projects that for some mysterious reason included ILSpy. In PVS-Studio, we use a pool of projects to test our C# analyzer. That group includes ILSpy, so it&#8217;s odd that we do not have an article on this project yet. But since I failed to find a Unity project for analysis, let&#8217;s take a look at ILSpy.<\/p>\n<p>Here&#8217;s the project&#8217;s description on <a href=\"https:\/\/github.com\/icsharpcode\/ILSpy\">GitHub<\/a>: ILSpy is the open-source .NET assembly browser and decompiler.<\/p>\n<p>Since there was no information on whether ILSpy&#8217;s developers use a static analyzer, I am going to assume PVS-Studio is the first one. This makes my tests and research even more interesting. Now, without further discussion, let&#8217;s move on to analysis results.<\/p>\n<h3>Replacement That Did Not Work<\/h3>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3038\/\">V3038<\/a> The &#8216;&#187;&#8216;&#187;&#8216; argument was passed to &#8216;Replace&#8217; method several times. It is possible that other argument should be passed instead. ICSharpCode.Decompiler ReflectionDisassembler.cs 772<\/p>\n<pre><code>private static void WriteSimpleValue(ITextOutput output,                                      object value, string typeName) {   switch (typeName)   {     case \"string\":       output.Write(  \"'\"                    + DisassemblerHelpers                       .EscapeString(value.ToString())                       .Replace(\"'\", \"\\'\")                   \/\/ &lt;=                    + \"'\");       break;     case \"type\":     ....   }   .... } <\/code><\/pre>\n<p>The author seems to be replacing all single quote character occurrences with a string consisting of two characters: a backslash and a single quote character. However, the developer missed a beat and by accident replaced the &#171;&#8216;&#187; with itself, thus performing a meaningless operation. There is no difference between assigning a string variable a value of &#171;&#8216;&#187; or &#171;\\'&#187; &#8212; either way the string is initialized with a single quote character. To include &#171;\\'&#187; in a string, use escape characters:  &#171;\\\\'&#187; or @&#187;\\'&#187;. Thus, one can change the <em>Replace<\/em> method call in the following way:<\/p>\n<pre><code>Replace(\"'\", @\"\\'\") <\/code><\/pre>\n<h3>Truth and Nothing but the Truth<\/h3>\n<p><strong>Warning 1<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3022\/\">V3022<\/a> Expression &#8216;negatedOp == BinaryOperatorType.Any&#8217; is always true. ICSharpCode.Decompiler CSharpUtil.cs <\/p>\n<pre><code>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)                  \/\/ &lt;=       return new UnaryOperatorExpression(....);     bOp = (BinaryOperatorExpression)bOp.Clone();     bOp.Operator = negatedOp;     return bOp;   } } <\/code><\/pre>\n<p>The analyzer warns that the <em>negatedOp<\/em> variable always equals to the value of <em>Any<\/em> from the <em>BinaryOperatorType<\/em> enumeration. To verify this, let us take a look at the <em>NegateRelationalOperator<\/em> method code that provides a value for the <em>negatedOp<\/em> variable.<\/p>\n<pre><code>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; } <\/code><\/pre>\n<p>If by the <em>NegateRelationalOperator<\/em> method call, the <em>bOp.Operator<\/em>&#8216;s value does not match any of the <em>case<\/em> labels, the method returns <em>BinaryOperatorType.Any<\/em>. You can see that the <em>NegateRelationalOperator<\/em> method is called only when <em>if<\/em> and <em>if else<\/em> statements above the method are evaluated to <em>false<\/em>. Moreover, if you look closely, you can notice that the <em>if<\/em> and <em>if<\/em> <em>else<\/em> statements cover all <em>case<\/em> labels the <em>NegateRelationalOperator<\/em> method contains. By the time the <em>NegateRelationalOperator<\/em> method is called, the <em>bOp.Operator<\/em> does not satisfy any of the <em>case<\/em> labels and the method returns the <em>BinaryOperatorType.Any<\/em> value. As a result, <em>negatedOp == BinaryOperatorType.Any<\/em> always evaluates to <em>true<\/em>, and the next line returns the value from the method. In addition, we get unreachable code:<\/p>\n<pre><code>bOp = (BinaryOperatorExpression)bOp.Clone(); bOp.Operator = negatedOp; return bOp; <\/code><\/pre>\n<p>By the way, the analyzer kindly issued a warning for this as well: <a href=\"https:\/\/www.viva64.com\/en\/w\/v3142\/\">V3142<\/a> Unreachable code detected. It is possible that an error is present. ICSharpCode.Decompiler CSharpUtil.cs 81<\/p>\n<p><strong>Warning 2<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3022\/\">V3022<\/a> Expression &#8216;pt != null&#8217; is always true. ICSharpCode.Decompiler FunctionPointerType.cs 168<\/p>\n<pre><code>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,    \/\/ &lt;=       ParameterReferenceKinds); } <\/code><\/pre>\n<p>Here everything is straightforward &#8212; the <em>else<\/em> branch is executed if the <em>pt<\/em> variable is not <em>null<\/em>. So I don&#8217;t see the need in a ternary operator that checks the <em>pt<\/em> variable for <em>null<\/em>. I suspect that in the past the code did not contain the <em>if<\/em>&#8212;<em>else<\/em> statement and the first <em>return<\/em> operator &#8212; then this check would have made sense. Right now it&#8217;s a good idea to remove the extra ternary operator: <\/p>\n<pre><code>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); } <\/code><\/pre>\n<p><strong>Warning 3<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3022\/\">V3022<\/a> Expression &#8216;settings.LoadInMemory&#8217; is always true. ICSharpCode.Decompiler CSharpDecompiler.cs 394<\/p>\n<pre><code>static PEFile LoadPEFile(string fileName, DecompilerSettings settings) {   settings.LoadInMemory = true;   return new PEFile(     fileName,     new FileStream(fileName, FileMode.Open, FileAccess.Read),     streamOptions: settings.LoadInMemory ?                           \/\/ &lt;=       PEStreamOptions.PrefetchEntireImage : PEStreamOptions.Default,     metadataOptions: settings.ApplyWindowsRuntimeProjections ?          MetadataReaderOptions.ApplyWindowsRuntimeProjections :         MetadataReaderOptions.None   ); } <\/code><\/pre>\n<p>This case is similar to the previous one &#8212; we get an unnecessary ternary operator. The <em>settings.LoadInMemory<\/em> property is set to <em>true<\/em> and this value does not change until the ternary operator checks the value. Here&#8217;s the code for the property&#8217;s getter and setter:<\/p>\n<pre><code>public bool LoadInMemory {   get { return loadInMemory; }   set {       if (loadInMemory != value)       {         loadInMemory = value;         OnPropertyChanged();       }   } } <\/code><\/pre>\n<p>It&#8217;s easy to exclude the unnecessary ternary operator and fix this code. There is probably no need to provide it here.<\/p>\n<p><strong>Warning 4<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3022\/\">V3022<\/a> Expression &#8216;ta&#8217; is always not null. The operator &#8216;??&#8217; is excessive. ICSharpCode.Decompiler ParameterizedType.cs 354<\/p>\n<pre><code>public IType VisitChildren(TypeVisitor visitor) {   ....   if (ta == null)       return this;   else       return new ParameterizedType(g, ta ?? typeArguments);     \/\/ &lt;= } <\/code><\/pre>\n<p>We can see the unnecessary <em>null<\/em> <em>coalescing<\/em> operator right away. When the <em>ta<\/em> variable gets to the <em>else<\/em> branch, it always has a value that is not <em>null<\/em>. Consequently, the ?? operator is excessive.<\/p>\n<p>I got a total of 31 warnings under the number of <a href=\"https:\/\/www.viva64.com\/en\/w\/v3022\/\">V3022<\/a>.<\/p>\n<h3>You Don&#8217;t Belong Here<\/h3>\n<p><strong>Warning 1<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3025\/\">V3025<\/a> Incorrect format. A different number of format items is expected while calling &#8216;Format&#8217; function. Arguments not used: End. ICSharpCode.Decompiler Interval.cs 269<\/p>\n<pre><code>public override string ToString() {   if (End == long.MinValue)   {     if (Start == long.MinValue)       return string.Format(\"[long.MinValue..long.MaxValue]\", End); \/\/ &lt;=     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);   } } <\/code><\/pre>\n<p>In the first <em>string.Format<\/em> method call, the format string does not match the arguments the method receives. The <em>End<\/em> variable&#8217;s value, passed as an argument, cannot be inserted into the format string, because the string lacks the {0} format element. Following the method&#8217;s logic, this is not an error and the <em>return<\/em> operator returns the string the code authors intended. This, of course, does not cancel the fact, that the code includes a useless <em>string.Format<\/em> method call with an unused argument. It&#8217;s a good idea to fix this to make the code clean and easy to read.<\/p>\n<p><strong>Warning 2<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3025\/\">V3025<\/a> Incorrect format. A different number of format items is expected while calling &#8216;AppendFormat&#8217; function. Arguments not used: angle. ILSpy.BamlDecompiler XamlPathDeserializer.cs 177<\/p>\n<pre><code>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);   .... } <\/code><\/pre>\n<p>In this case the <em>angle<\/em> variable was left out. Though the developer passed the variable to the <em>AppendFormat<\/em> method, the variable remains unused, because the format string contains two of {2} format elements and lacks the {1} format element. The authors probably intended to produce the following string:<em>&#171;A{0} {1:R} {2} {3} {4}&#187;<\/em>.<\/p>\n<h3>Double Standards<\/h3>\n<p><strong>Warning 1<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3095\/\">V3095<\/a> The &#8216;roslynProject&#8217; object was used before it was verified against null. Check lines: 96, 97. ILSpy.AddIn OpenILSpyCommand.cs 96<\/p>\n<pre><code>protected Dictionary&lt;string, detectedreference=\"\"> GetReferences(....) {   ....   var roslynProject =  owner.Workspace                             .CurrentSolution                             .GetProject(projectReference.ProjectId);   var project = FindProject(owner.DTE.Solution                                  .Projects.OfType&lt;envdte.project>(),                             roslynProject.FilePath);              \/\/ &lt;=    if (roslynProject != null &amp;&amp; project != null)                   \/\/ &lt;=   .... } <\/code><\/pre>\n<p>First we get a <em>roslynProject<\/em> object&#8217;s <em>FilePath<\/em> property with no worry that the <em>roslynProject<\/em> value may be <em>null<\/em>, and in the next line we check <em>roslynProject<\/em> for <em>null<\/em>. Such code does not look safe and may produce a <em>NullReferenceException<\/em> exception. To fix this code, one can use the <em>FilePath<\/em> property along with a null-conditional operator. The second step is to plan for the <em>FindProject<\/em> method to potentially get a <em>null<\/em> value as the last parameter.<\/p>\n<p><strong>Warning 2<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3095\/\">V3095<\/a> The &#8216;listBox&#8217; object was used before it was verified against null. Check lines: 46, 52. ILSpy FlagsFilterControl.xaml.cs 46<\/p>\n<pre><code>public override void OnApplyTemplate() {   base.OnApplyTemplate();    listBox = Template.FindName(\"ListBox\", this) as ListBox;   listBox.ItemsSource = FlagGroup.GetFlags(....);         \/\/ &lt;=    var filter = Filter;    if (filter == null || filter.Mask == -1)   {     listBox?.SelectAll();                                 \/\/ &lt;=   } } <\/code><\/pre>\n<p>This case is similar to the previous example. First, we assign a value to the <em>ItemsSource<\/em> property and do not check whether the <em>listBox<\/em> variable contains <em>null<\/em>. Then, several lines later, I can see the <em>listBox<\/em> variable with the null-conditional operator. Note that between these two calls the <em>listBox<\/em> variable did not get a new value.<\/p>\n<p>Our analyzer displayed 10 warnings with number <a href=\"https:\/\/www.viva64.com\/en\/w\/v3095\/\">V3095<\/a>. Here is a list of those warnings:<\/p>\n<ul>\n<li>\n<p>V3095 The &#8216;pV&#8217; object was used before it was verified against null. Check lines: 761, 765. ICSharpCode.Decompiler TypeInference.cs 761<\/p>\n<\/li>\n<li>\n<p>V3095 The &#8216;pU&#8217; object was used before it was verified against null. Check lines: 882, 886. ICSharpCode.Decompiler TypeInference.cs 882<\/p>\n<\/li>\n<li>\n<p>V3095 The &#8216;finalStore&#8217; object was used before it was verified against null. Check lines: 261, 262. ICSharpCode.Decompiler TransformArrayInitializers.cs 261<\/p>\n<\/li>\n<li>\n<p>V3095 The &#8216;definitionDeclaringType&#8217; object was used before it was verified against null. Check lines: 93, 104. ICSharpCode.Decompiler SpecializedMember.cs 93<\/p>\n<\/li>\n<li>\n<p>V3095 The &#8216;TypeNamespace&#8217; object was used before it was verified against null. Check lines: 84, 88. ILSpy.BamlDecompiler XamlType.cs 84<\/p>\n<\/li>\n<li>\n<p>V3095 The &#8216;property.Getter&#8217; object was used before it was verified against null. Check lines: 1676, 1684. ICSharpCode.Decompiler CSharpDecompiler.cs 1676<\/p>\n<\/li>\n<li>\n<p>V3095 The &#8216;ev.AddAccessor&#8217; object was used before it was verified against null. Check lines: 1709, 1717. ICSharpCode.Decompiler CSharpDecompiler.cs 1709<\/p>\n<\/li>\n<li>\n<p>V3095 The &#8216;targetType&#8217; object was used before it was verified against null. Check lines: 1614, 1657. ICSharpCode.Decompiler CallBuilder.cs 1614<\/p>\n<\/li>\n<\/ul>\n<p>By the way, if you want to check your own project with the PVS-Studio analyzer or recheck ILSpy to see all warnings by yourself, you can <a href=\"https:\/\/www.viva64.com\/en\/pvs-studio-download\/\">try the analyzer<\/a>. On the PVS-Studio website, you can both download the analyzer and request a trial license.<\/p>\n<h3>All Roads Lead to One Place<\/h3>\n<p><strong>Warning 1<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3139\/\">V3139<\/a> Two or more case-branches perform the same actions. ILSpy Images.cs 251<\/p>\n<pre><code>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;             \/\/ &lt;=       break;     case MemberIcon.EnumValue:       baseImage = Images.Literal;             \/\/ &lt;=       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; } <\/code><\/pre>\n<p>As I see it, this is clearly a mistake. If the <em>icon<\/em> variable&#8217;s value equals <em>MemberIcon.EnumValue<\/em>, then the <em>baseImage<\/em> variable in the <em>case<\/em> branch must get the value of <em>Images.EnumValue<\/em>. This is a good example of an error that a static analyzer easily detects and a human eye easily misses when looking through code.<\/p>\n<p><strong>Warning 2<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3139\/\">V3139<\/a> Two or more case-branches perform the same actions. ICSharpCode.Decompiler CSharpConversions.cs 829<\/p>\n<pre><code>bool ImplicitConstantExpressionConversion(ResolveResult rr, IType toType) {   ....   switch (toTypeCode)   {     case TypeCode.SByte:       return val >= SByte.MinValue &amp;&amp; val &lt;= SByte.MaxValue;     case TypeCode.Byte:       return val >= Byte.MinValue &amp;&amp; val &lt;= Byte.MaxValue;     case TypeCode.Int16:       return val >= Int16.MinValue &amp;&amp; val &lt;= Int16.MaxValue;     case TypeCode.UInt16:       return val >= UInt16.MinValue &amp;&amp; val &lt;= UInt16.MaxValue;     case TypeCode.UInt32:       return val >= 0;                 \/\/ &lt;=     case TypeCode.UInt64:       return val >= 0;                 \/\/ &lt;=   }   .... } <\/code><\/pre>\n<p>I won&#8217;t claim that the analyzer found here an obvious mistake, but the warning definitely makes sense. If the <em>case<\/em> labels for the <em>TypeCode.UInt32<\/em> and <em>TypeCode.UInt64<\/em> perform the same set of actions, why not write shorter code:<\/p>\n<pre><code>bool ImplicitConstantExpressionConversion(ResolveResult rr, IType toType) {   switch (toTypeCode)   {       ....       case TypeCode.UInt32:       case TypeCode.UInt64:         return val >= 0;   }   .... } <\/code><\/pre>\n<p>The analyzer issued 2 more warnings with the number <a href=\"https:\/\/www.viva64.com\/en\/w\/v3139\/\">V3139<\/a>:<\/p>\n<ul>\n<li>\n<p>V3139 Two or more case-branches perform the same actions. ICSharpCode.Decompiler EscapeInvalidIdentifiers.cs 85<\/p>\n<\/li>\n<li>\n<p>V3139 Two or more case-branches perform the same actions. ICSharpCode.Decompiler TransformExpressionTrees.cs 370<\/p>\n<\/li>\n<\/ul>\n<h3>Safety Comes First<\/h3>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3083\/\">V3083<\/a> Unsafe invocation of event, NullReferenceException is possible. Consider assigning event to a local variable before invoking it. ILSpy MainWindow.xaml.cs 787class ResXResourceWriter : IDisposable<\/p>\n<pre><code>void assemblyList_Assemblies_CollectionChanged(....) {   ....   if (CurrentAssemblyListChanged != null)     CurrentAssemblyListChanged(this, e);      \/\/ &lt;= } <\/code><\/pre>\n<p>This way to raise events is fairly common, but the fact that we see this pattern in many projects is not an excuse to use it. Of course, this is not a critical error, but, as the analyzer&#8217;s warning says &#8212; this event invocation is not safe and a <em>NullReferenceException<\/em> exception is possible. If all handlers unsubscribe from the event after *CurrentAssemblyListChanged *is checked for <em>null<\/em> and before the event is raised (for example, in a different thread), then a <em>NullReferenceException<\/em> exception is thrown. One can fix this and write the following safe code instead:<\/p>\n<pre><code>void assemblyList_Assemblies_CollectionChanged(....) {   ....   CurrentAssemblyListChanged?.Invoke(this, e); } <\/code><\/pre>\n<p>PVS-Studio found 8 more similar cases, they can all be fixed with the approach above.<\/p>\n<h3>Confident Uncertainty<\/h3>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3146\/\">V3146<\/a> Possible null dereference. The &#8216;FirstOrDefault&#8217; can return default null value. ILSpy.BamlDecompiler BamlResourceEntryNode.cs 76<\/p>\n<pre><code>bool LoadBaml(AvalonEditTextOutput output, CancellationToken cancellationToken) {   var asm = this.Ancestors().OfType&lt;assemblytreenode>()                             .FirstOrDefault().LoadedAssembly;       \/\/ &lt;=   ....   return true; } <\/code><\/pre>\n<p>Here the developer calls the <em>FirstOrDefault<\/em> method to get the first available <em>AssemblyTreeNode<\/em> type element from the collection the <em>OfType<\/em> method returns. If the collection is empty or does not contain any elements that meet the search criteria, the <em>FirstOrDefault<\/em> method returns the default value &#8212; in our case it&#8217;s <em>null<\/em>. A further attempt to access the the <em>LoadedAssembly<\/em> property means using a null reference and yields a <em>NullReferenceException<\/em> exception. To avoid this situation, it&#8217;s a good idea to use a null-conditional operator:<\/p>\n<pre><code>bool LoadBaml(AvalonEditTextOutput output, CancellationToken cancellationToken) {   var asm = this.Ancestors().OfType&lt;assemblytreenode>()                             .FirstOrDefault()?.LoadedAssembly;     \/\/ &lt;=   ....   return true; } <\/code><\/pre>\n<p>We can assume the developer intended for the <em>FirstOrDefault<\/em> method to never return <em>null<\/em> in this particular place. If this is really the case, then it&#8217;s a good idea to call the <em>First<\/em> method instead of <em>FirstOrDefault<\/em>, because it is a way to stress the developer&#8217;s assurance that the method is always able to retrieve the required element from the collection. Moreover, if the element is not found in the collection, the developer gets the <em>InvalidOperationException<\/em> exception, which displays the following message: &#171;Sequence contains no elements&#187;. This is more informative than a <em>NullReferenceException<\/em> exception that is thrown after the code refers to a null value the <em>FirstOrDefault<\/em> method returns.<\/p>\n<h3>Unsafe Scanning<\/h3>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3105\/\">V3105<\/a> The &#8216;m&#8217; variable was used after it was assigned through null-conditional operator. NullReferenceException is possible. ILSpy MethodVirtualUsedByAnalyzer.cs 137<\/p>\n<pre><code>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               \/\/ &lt;=           &amp;&amp; m.ParentModule.PEFile == analyzedMethod.ParentModule.PEFile)  \/\/ &lt;=       {         return true;       }       break;   }   .... } <\/code><\/pre>\n<p>In the code above, the developers used the null conditional operator to initialize the <em>m<\/em> variable. They anticipated that <em>m<\/em> could be assigned a <em>null<\/em> value. Interestingly, in the next line the developers get the <em>m<\/em> variable&#8217;s properties and do not use the null conditional operator. This may lead to <em>NullReferenceException<\/em> type exceptions. As in some other examples we&#8217;ve reviewed so far, let&#8217;s fix the problem by introducing the null-conditional operator:<\/p>\n<pre><code>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           &amp;&amp; m?.ParentModule.PEFile == analyzedMethod.ParentModule.PEFile)       {         return true;       }       break;   }   .... } <\/code><\/pre>\n<h3>Good Old Friends<\/h3>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3070\/\">V3070<\/a> Uninitialized variable &#8216;schema&#8217; is used when initializing the &#8216;ResourceSchema&#8217; variable. ICSharpCode.Decompiler ResXResourceWriter.cs 63<\/p>\n<pre><code>class ResXResourceWriter : IDisposable {   ....   public static readonly string ResourceSchema = schema;   ....   static string schema = ....;   .... } <\/code><\/pre>\n<p>At first I did not plan to list this warning, because about five years ago we found an identical error in the <a href=\"https:\/\/www.viva64.com\/en\/b\/0431\/\">Mono<\/a> project. But then I talked to a colleague and we decided the error is worth mentioning. As the article dedicated to analyzing Mono describes, by the time the <em>schema<\/em> static field initializes the <em>ResourceSchema<\/em> static field, the <em>schema<\/em> static field has not been initialized yet and evaluates to its default value &#8212; <em>null<\/em>. The ResXResourceWriter.cs file where we found the error, was kindly borrowed with copyright preservation from the Mono project. Then developers expanded the file with unique features for the ILSpy project. This is how bugs from projects spread across the internet and migrate from one project to another. By the way, the original developers have not yet fixed the bug in the original file.<\/p>\n<h3>Conclusion<\/h3>\n<p>Ultimately, the ILSpy decompiler&#8217;s code analysis demonstrated that the project would benefit from a static code analyzer. Some code fragments we described are not errors, but refactoring them will clean up the code. Other code snippets are clearly incorrect. It is obvious that the authors expect a different result &#8212; for example the <em>Replace<\/em> method&#8217;s behavior that has the same arguments. Regular use of static analysis allows developers to find and fix incorrect, ambiguous or excessive code. It is always quicker and cheaper to fix a bug at the stage of writing or testing code, than after the product is released with a bug and the users come and tell you &#171;Hey, there&#8217;s a bug here&#187; &#8212; and you&#8217;re lucky if they use these words. It&#8217;s always better if the static analyzer tells you this. Thank you for reading.<\/p>\n<h3>A Note for Those Looking to Test ILSpy on Their Own<\/h3>\n<p>When analyzing the ILSpy project, we found a few problems related to the analyzer itself &#8212; yes, things like this happen. We fixed the issues, but the changes were not included in the 7.11 release. They will be available in the next version. Also note that ILSpy is compiled slightly differently from what most developers are used to. This peculiarity requires additional analyzer settings. So if you want to check ILSpy by yrself &#8212; <a href=\"https:\/\/www.viva64.com\/en\/about-feedback\/\">let us know<\/a>. We will provide you with the analyzer&#8217;s beta and explain how to set up the analys<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/540934\/\"> https:\/\/habr.com\/ru\/articles\/540934\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>In PVS-Studio, we often check various compilers&#8217; code and post the results in our blog. Decompiler programs, however, seem to be a bit neglected. To restore justice in this world, we analyzed the ILSpy decompiler&#8217;s source code. Let&#8217;s take a look at the peculiar things PVS-Studio found.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<h3>Introduction<\/h3>\n<p>Probably almost every programmer used a decompiler at least once. The reasons could vary: to see how a method is implemented, to check if there is an error inside a library used, or to satisfy curiosity and look up some source code. At the mention of a decompiler, most .NET programmers will think of dotPeek or ILSpy. .NET Reflector is not as popular anymore. I remember when I first learned about these utilities and decompiled someone else&#8217;s library &#8212; a thought of espionage ran through my head. I was obviously not the only one thinking along these lines &#8212; I am sure ILSpy&#8217;s name is not accidental. I was curious what the spy is made of and wanted to reinforce it with a static analyzer. So I used the PVS-Studio analyzer on ILSpy&#8217;s source code and put together an article based on the most interesting and suspicious code fragments I found.<\/p>\n<p>To be honest, this article on ILSpy just sort of happened. Some of our clients are game development studios. This is one of the reasons why we try to make our tool as helpful and handy as possible for game developers, especially for those who employ Unity and Unreal Engine.<\/p>\n<p>While I know many clients who work with Unreal Engine, I don&#8217;t encounter that many Unity developers who use our analyzer. I want to encourage them to try the PVS-Studio analyzer, because I believe the Unity community can benefit from it. A cool way to demonstrate it would be to analyze a Unity-based open-source game and present the results. But the problem is &#8212; I could not find such a game! So please <a href=\"https:\/\/github.com\/viva64\/pvs-studio-check-list\">let me know of any ideas<\/a> you have for such games I could analyze with PVS-Studio. When I did try to look for a Unity-based open-source game, my search yielded unexpected results. On one <a href=\"https:\/\/awesomeopensource.com\/projects\/unity\">website<\/a>, I found a list of Unity projects that for some mysterious reason included ILSpy. In PVS-Studio, we use a pool of projects to test our C# analyzer. That group includes ILSpy, so it&#8217;s odd that we do not have an article on this project yet. But since I failed to find a Unity project for analysis, let&#8217;s take a look at ILSpy.<\/p>\n<p>Here&#8217;s the project&#8217;s description on <a href=\"https:\/\/github.com\/icsharpcode\/ILSpy\">GitHub<\/a>: ILSpy is the open-source .NET assembly browser and decompiler.<\/p>\n<p>Since there was no information on whether ILSpy&#8217;s developers use a static analyzer, I am going to assume PVS-Studio is the first one. This makes my tests and research even more interesting. Now, without further discussion, let&#8217;s move on to analysis results.<\/p>\n<h3>Replacement That Did Not Work<\/h3>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3038\/\">V3038<\/a> The &#8216;&#187;&#8216;&#187;&#8216; argument was passed to &#8216;Replace&#8217; method several times. It is possible that other argument should be passed instead. ICSharpCode.Decompiler ReflectionDisassembler.cs 772<\/p>\n<pre><code>private static void WriteSimpleValue(ITextOutput output,                                      object value, string typeName) {   switch (typeName)   {     case \"string\":       output.Write(  \"'\"                    + DisassemblerHelpers                       .EscapeString(value.ToString())                       .Replace(\"'\", \"\\'\")                   \/\/ &lt;=                    + \"'\");       break;     case \"type\":     ....   }   .... } <\/code><\/pre>\n<p>The author seems to be replacing all single quote character occurrences with a string consisting of two characters: a backslash and a single quote character. However, the developer missed a beat and by accident replaced the &#171;&#8216;&#187; with itself, thus performing a meaningless operation. There is no difference between assigning a string variable a value of &#171;&#8216;&#187; or &#171;\\'&#187; &#8212; either way the string is initialized with a single quote character. To include &#171;\\'&#187; in a string, use escape characters:  &#171;\\\\'&#187; or @&#187;\\'&#187;. Thus, one can change the <em>Replace<\/em> method call in the following way:<\/p>\n<pre><code>Replace(\"'\", @\"\\'\") <\/code><\/pre>\n<h3>Truth and Nothing but the Truth<\/h3>\n<p><strong>Warning 1<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3022\/\">V3022<\/a> Expression &#8216;negatedOp == BinaryOperatorType.Any&#8217; is always true. ICSharpCode.Decompiler CSharpUtil.cs <\/p>\n<pre><code>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)                  \/\/ &lt;=       return new UnaryOperatorExpression(....);     bOp = (BinaryOperatorExpression)bOp.Clone();     bOp.Operator = negatedOp;     return bOp;   } } <\/code><\/pre>\n<p>The analyzer warns that the <em>negatedOp<\/em> variable always equals to the value of <em>Any<\/em> from the <em>BinaryOperatorType<\/em> enumeration. To verify this, let us take a look at the <em>NegateRelationalOperator<\/em> method code that provides a value for the <em>negatedOp<\/em> variable.<\/p>\n<pre><code>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; } <\/code><\/pre>\n<p>If by the <em>NegateRelationalOperator<\/em> method call, the <em>bOp.Operator<\/em>&#8216;s value does not match any of the <em>case<\/em> labels, the method returns <em>BinaryOperatorType.Any<\/em>. You can see that the <em>NegateRelationalOperator<\/em> method is called only when <em>if<\/em> and <em>if else<\/em> statements above the method are evaluated to <em>false<\/em>. Moreover, if you look closely, you can notice that the <em>if<\/em> and <em>if<\/em> <em>else<\/em> statements cover all <em>case<\/em> labels the <em>NegateRelationalOperator<\/em> method contains. By the time the <em>NegateRelationalOperator<\/em> method is called, the <em>bOp.Operator<\/em> does not satisfy any of the <em>case<\/em> labels and the method returns the <em>BinaryOperatorType.Any<\/em> value. As a result, <em>negatedOp == BinaryOperatorType.Any<\/em> always evaluates to <em>true<\/em>, and the next line returns the value from the method. In addition, we get unreachable code:<\/p>\n<pre><code>bOp = (BinaryOperatorExpression)bOp.Clone(); bOp.Operator = negatedOp; return bOp; <\/code><\/pre>\n<p>By the way, the analyzer kindly issued a warning for this as well: <a href=\"https:\/\/www.viva64.com\/en\/w\/v3142\/\">V3142<\/a> Unreachable code detected. It is possible that an error is present. ICSharpCode.Decompiler CSharpUtil.cs 81<\/p>\n<p><strong>Warning 2<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3022\/\">V3022<\/a> Expression &#8216;pt != null&#8217; is always true. ICSharpCode.Decompiler FunctionPointerType.cs 168<\/p>\n<pre><code>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,    \/\/ &lt;=       ParameterReferenceKinds); } <\/code><\/pre>\n<p>Here everything is straightforward &#8212; the <em>else<\/em> branch is executed if the <em>pt<\/em> variable is not <em>null<\/em>. So I don&#8217;t see the need in a ternary operator that checks the <em>pt<\/em> variable for <em>null<\/em>. I suspect that in the past the code did not contain the <em>if<\/em>&#8212;<em>else<\/em> statement and the first <em>return<\/em> operator &#8212; then this check would have made sense. Right now it&#8217;s a good idea to remove the extra ternary operator: <\/p>\n<pre><code>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); } <\/code><\/pre>\n<p><strong>Warning 3<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3022\/\">V3022<\/a> Expression &#8216;settings.LoadInMemory&#8217; is always true. ICSharpCode.Decompiler CSharpDecompiler.cs 394<\/p>\n<pre><code>static PEFile LoadPEFile(string fileName, DecompilerSettings settings) {   settings.LoadInMemory = true;   return new PEFile(     fileName,     new FileStream(fileName, FileMode.Open, FileAccess.Read),     streamOptions: settings.LoadInMemory ?                           \/\/ &lt;=       PEStreamOptions.PrefetchEntireImage : PEStreamOptions.Default,     metadataOptions: settings.ApplyWindowsRuntimeProjections ?          MetadataReaderOptions.ApplyWindowsRuntimeProjections :         MetadataReaderOptions.None   ); } <\/code><\/pre>\n<p>This case is similar to the previous one &#8212; we get an unnecessary ternary operator. The <em>settings.LoadInMemory<\/em> property is set to <em>true<\/em> and this value does not change until the ternary operator checks the value. Here&#8217;s the code for the property&#8217;s getter and setter:<\/p>\n<pre><code>public bool LoadInMemory {   get { return loadInMemory; }   set {       if (loadInMemory != value)       {         loadInMemory = value;         OnPropertyChanged();       }   } } <\/code><\/pre>\n<p>It&#8217;s easy to exclude the unnecessary ternary operator and fix this code. There is probably no need to provide it here.<\/p>\n<p><strong>Warning 4<\/strong><\/p>\n<p><a href=\"https:\/\/www.viva64.com\/en\/w\/v3022\/\">V3022<\/a> Expression &#8216;ta&#8217; is always not null. The operator &#8216;??&#8217; is excessive. ICSharpCode.Decompiler ParameterizedType.cs 354<\/p>\n<pre><code>public IType VisitChildren(TypeVisitor visitor) {   ....   if (ta == null)       return this;   else       return new ParameterizedType(g, ta ?? typeArguments);     \/\/ &lt;= } <\/code><\/pre>\n<p>We can see the unnecessary <em>null<\/em> <em>coal<\/em><\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-382751","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/382751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=382751"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/382751\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=382751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=382751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=382751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}