通过PVS-Studio静态分析器验证.NET Core库的源代码

图片19

.NET Core库是GitHub上最受欢迎的C#项目之一。 鉴于其广泛的流行性和可用性,不足为奇。 试图找出在这些库的源代码中可以找到哪些暗角,这将变得更加有趣,我们将尝试使用PVS-Studio静态分析器来做到这一点。 您认为您最终发现了什么有趣的东西吗?

我去这篇文章已经一年半了。 在某个时候,我的脑海里浮现出一个想法:.NET Core库是一个花哨的东西,将它们检出将很有趣。 我几次检查该项目时,分析器发现了越来越多的有趣地方,但是它并没有超出警告列表的快速滚动范围。 这就是-完成! 项目已选中,文章就在您眼前。

有关项目和分析的更多信息


如果您热衷于分析代码-您可以跳过本节,但我真的很想阅读它-在这里我将更多地讨论项目和分析器,以及有关如何分析和再现错误的一些知识。

审核项目


可能不告诉您什么是CoreFX(.NET Core库),但是,如果您没有听到,则在下面进行描述。 我没有改写它的意思,而是从GitHub上的项目页面上获取的 ,您还可以在其中下载源代码。

说明: 此存储库包含.NET Core的库实现(称为“ CoreFX”)。 它包括System.Collections,System.IO,System.Xml和许多其他组件。 相应的.NET Core运行时存储库(称为“ CoreCLR”)包含.NET Core的运行时实现。 它包括RyuJIT,.NET GC和许多其他组件。 运行时特定的库代码(System.Private.CoreLib)位于CoreCLR存储库中。 它需要与运行时一起构建和版本控制。 其余CoreFX与运行时实现无关,可以在任何兼容的.NET运行时(例如CoreRT)上运行

二手分析仪及分析方法


我使用PVS-Studio静态分析器检查了源代码。 通常,PVS-Studio不仅可以分析C#代码,还可以分析C,C ++,Java。 到目前为止,对C#代码的分析仅适用于Windows,而您可以在Windows,Linux,macOS下对C,C ++,Java中的代码进行分析。

我通常使用Visual Studio的PVS-Studio插件来测试C#项目(支持2010-2019版本),因为这可能是最简单,最方便的分析方法:打开解决方案,开始分析,处理警告列表。 但是,使用CoreFX,事情变得更加复杂。

事实是该项目没有单个.sln文件,因此,在Visual Studio中打开该文件并使用PVS-Studio插件进行全面分析将失败。 可能很好,我真的不知道Visual Studio如何处理这种规模的解决方案。

但是,分析没有问题,因为PVS-Studio分发工具包包括用于MSBuild项目(实际上是.sln)的分析器的命令行版本。 我需要做的就是编写一个小脚本,为CoreFX目录中的每个.sln运行“ PVS-Studio_Cmd.exe”,并将分析结果放在单独的目录中(由分析器启动标志指示)。

瞧! -在出口处,我有一组日志,其中包含很多有趣的东西。 如果需要,可以使用分发工具包随附的PlogConverter实用程序合并日志。 但是使用单个日志对我来说更方便,因此我没有开始将它们组合在一起。

在描述一些错误时,我参考了docs.microsoft.com上的文档以及可从nuget.org下载的NuGet软件包。 我承认文档中描述的代码/在软件包中找到的代码可能与所分析的代码略有不同。 但是,例如,如果在文档中没有描述针对许多输入数据生成的异常的情况,并且在软件包的新版本中它们将出现,这将是非常奇怪的-同意,这将是令人怀疑的惊喜。 在用于调试库的相同输入数据上,从NuGet复制程序包中的错误表明该问题不是新问题,更重要的是,无需从源代码构建项目,就可以“解决”该问题。

因此,假设存在一些理论上的代码错误同步的可能性,我发现可以参考docs.microsoft.com上相应方法的描述,并在nuget.org的程序包中重现问题。

我还注意到,在撰写本文期间,提供的链接的描述以及程序包(其他版本)中的信息(注释)可能会更改。

其他经过验证的项目


顺便说一句,这不是一篇独特的文章,我们还会撰写其他有关检查项目的文章, 可在此处找到其列表。 而且,在该站点上,我们不仅收集了有关项目分析的文章,还收集了有关C,C ++,C#,Java的各种技术文章,以及有趣的注释。 您可以在博客上找到所有这些内容。

我的同事之前在2015年测试过.NET Core库。 先前分析的结果可以在相应的文章中找到:“ .NET核心库(CoreFX)的新年检查 ”。

发现错误,可疑和有趣的地方


与往常一样,出于更大的兴趣,我建议您首先自己搜索片段中的错误,然后再阅读分析器警告和问题描述。

为方便起见,我使用问题N形式的标签将要考虑的片段彼此明确地分开-更容易理解一个错误的描述在哪里结束而另一个错误的分析从哪里开始。 是的,并且引用特定片段也更容易。

第1期

abstract public class Principal : IDisposable { .... public void Save(PrincipalContext context) { .... if ( context.ContextType == ContextType.Machine || _ctx.ContextType == ContextType.Machine) { throw new InvalidOperationException( SR.SaveToNotSupportedAgainstMachineStore); } if (context == null) { Debug.Assert(this.unpersisted == true); throw new InvalidOperationException(SR.NullArguments); } .... } .... } 

PVS-Studio警告V3095在对null进行验证之前,已使用“上下文”对象。 检查行:340,346。Principal.cs 340

开发人员明确指出context参数的null值无效,并希望强调这一点,但类型为InvalidOperationException 。 但是,在更高的条件下,在先前条件下, 上下文链接-context.ContextType会无条件取消引用。 结果,如果context的值为null ,则将抛出 NullReferenceException类型的异常,而不是预期的InvalidOperationExcetion

让我们尝试重现该问题。 将适当的库( System.DirectoryServices.AccountManagement )连接到项目,并执行以下代码:

 GroupPrincipal groupPrincipal = new GroupPrincipal(new PrincipalContext(ContextType.Machine)); groupPrincipal.Save(null); 

GroupPrincipal是抽象类Principal的后继者,该类包含我们需要的Save方法的实现。 我们运行代码以执行代码,然后查看需要证明的内容。

图片1


为了娱乐,您可以尝试从NuGet下载适当的软件包,然后尝试以相同的方式重复该问题。 我安装了4.5.0版的软件包,并得到了预期的结果。

图片2


第2期

 private SearchResultCollection FindAll(bool findMoreThanOne) { searchResult = null; DirectoryEntry clonedRoot = null; if (_assertDefaultNamingContext == null) { clonedRoot = SearchRoot.CloneBrowsable(); } else { clonedRoot = SearchRoot.CloneBrowsable(); } .... } 

PVS-Studio警告V3004'then '语句等效于'else'语句。 DirectorySearcher.cs 629

不管_assertDefaultNamingContext == null条件是否成立,都将执行相同的操作, 此后if语句 分支具有相同的主体。 在某个分支中应该有另一个动作,或者可以省略if语句,以免混淆程序员和分析器。

第3期

 public class DirectoryEntry : Component { .... public void RefreshCache(string[] propertyNames) { .... object[] names = new object[propertyNames.Length]; for (int i = 0; i < propertyNames.Length; i++) names[i] = propertyNames[i]; .... if (_propertyCollection != null && propertyNames != null) .... .... } .... } 

PVS-Studio警告V3095在对null进行验证之前,已使用'propertyNames'对象。 检查行:990、1004。DirectoryEntry.cs 990

我们再次看到一个奇怪的过程。 该方法具有propertyNames!=空检查,即 开发人员请确保该方法返回null 。 在上面,您可以观察到对该可能为空的引用的多个访问-propertyNames.LengthpropertyNames [i] 。 结果是可以预料的-如果将null引用传递给方法, 则会发生NullReferenceExcepption类型的异常。

RefreshCache是公共类中的公共方法,这是一个巧合。 尝试重复该问题? 为此,将所需的库-System.DirectoryServices连接到项目,并编写如下代码:

 DirectoryEntry de = new DirectoryEntry(); de.RefreshCache(null); 

运行代码以执行并查看预期的图片。

图片3


为了好玩,您可以尝试在NuGet软件包的发行版上重现该问题。 我们将System.DirectoryServices包连接到NuGet项目(我使用的是4.5.0版),然后运行已经熟悉的代码来执行。 结果较低。

图片4


第4期

现在我们从相反的角度出发-首先尝试编写使用该类实例的代码,然后再查看内部。 让我们来看一下System.Drawing.Common库中的System.Drawing.CharacterRange的结构和同名的NuGet包。

使用的代码如下:

 CharacterRange range = new CharacterRange(); bool eq = range.Equals(null); Console.WriteLine(eq); 

为了以防万一,要刷新内存,我们将转到docs.microsoft.com来记住表达式obj.Equals(null)期望返回的值:

对于Equals(Object)方法的所有实现,以下语句必须为true。 在列表中,x,y和z表示不为null的对象引用。

....

x.Equals(null)返回false。

您是否认为“ False”将显示在控制台中? 当然不是,那太容易了。 :)我们执行代码并查看结果。

图片5

这是使用NuGet包System.Drawing.Common 4.5.1执行上述代码时的结论。 我们使用库的调试版本运行相同的代码,然后查看以下内容:

图片6


现在让我们看一下源代码-CharacterRange结构中Equals方法的实现以及分析器警告:

 public override bool Equals(object obj) { if (obj.GetType() != typeof(CharacterRange)) return false; CharacterRange cr = (CharacterRange)obj; return ((_first == cr.First) && (_length == cr.Length)); } 

PVS-Studio 警告V3115将 “ null”传递给“ Equals”方法不应导致“ NullReferenceException”。 字符范围56

我们看到了需要证明的内容-obj参数处理不正确,因此在调用GetType实例方法时,条件表达式中会发生NullReferenceException类型的异常。

第5期

浏览此库时,请考虑另一个有趣的地方-Icon.Save方法。 在研究之前,请参见方法说明。

没有该方法的描述:

图片7

我们转向docs.microsoft.com-“ Icon.Save(流)方法 ”。 但是,这里对输入值也没有限制,也没有有关生成的异常的信息。

现在让我们继续进行代码研究。

 public sealed partial class Icon : MarshalByRefObject, ICloneable, IDisposable, ISerializable { .... public void Save(Stream outputStream) { if (_iconData != null) { outputStream.Write(_iconData, 0, _iconData.Length); } else { .... if (outputStream == null) throw new ArgumentNullException("dataStream"); .... } } .... } 

PVS-Studio警告V3095在对null进行验证之前,已使用'outputStream'对象。 检查行:654、672。Icon.Windows.cs 654

同样,我们已经知道的故事是可能取消引用null,因为方法参数被取消引用而不检查null 。 同样,情况(类和方法)的良好组合是公开的,这意味着您可以尝试重现该问题。

任务很简单-将代码执行带到表达式outputStream.Write(_iconData,0,_iconData.Length); 同时保持变量outputStream - null的值 。 为此,满足条件_iconData!= Null就足够了。

让我们看一下最简单的公共构造函数:

 public Icon(string fileName) : this(fileName, 0, 0) { } 

他只是将工作委托给另一个构造函数。 好吧,进一步看-这里使用的构造函数。

 public Icon(string fileName, int width, int height) : this() { using (FileStream f = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { Debug.Assert(f != null, "File.OpenRead returned null instead of throwing an exception"); _iconData = new byte[(int)f.Length]; f.Read(_iconData, 0, _iconData.Length); } Initialize(width, height); } 

这就是您需要的。 调用此构造函数后,如果我们成功地从文件中读取了数据,并且在Initialize方法中没有发生崩溃,则_iconData字段将包含指向某些对象的链接,这正是我们所需要的。

事实证明,要重现此问题,您需要使用实际的图标创建Icon类的实例,然后调用Save方法,并将null作为参数传递,我们将这样做。 该代码可能如下所示:

 Icon icon = new Icon(@"D:\document.ico"); icon.Save(null); 

执行结果是预期的。

图片8

第6期

我们继续进行审查,然后转到System.Management库。 尝试找出在情况 CimType.UInt32和其余情况下执行的操作之间的3个差异。

 private static string ConvertToNumericValueAndAddToArray(....) { string retFunctionName = string.Empty; enumType = string.Empty; switch(cimType) { case CimType.UInt8: case CimType.SInt8: case CimType.SInt16: case CimType.UInt16: case CimType.SInt32: arrayToAdd.Add(System.Convert.ToInt32( numericValue, (IFormatProvider)CultureInfo.InvariantCulture .GetFormat(typeof(int)))); retFunctionName = "ToInt32"; enumType = "System.Int32"; break; case CimType.UInt32: arrayToAdd.Add(System.Convert.ToInt32( numericValue, (IFormatProvider)CultureInfo.InvariantCulture .GetFormat(typeof(int)))); retFunctionName = "ToInt32"; enumType = "System.Int32"; break; } return retFunctionName; } 

当然,没有任何差异,分析仪会发出警告。

PVS-Studio警告V3139两个或更多分支机构执行相同的操作。 WMIGenerator.cs 5220

我个人对这种代码风格不太清楚。 如果这里没有错误,我认为将相同的逻辑分散到不同的案例中是不值得的。

第7期

Microsoft.CSharp库。

 private static IList<KeyValuePair<string, object>> QueryDynamicObject(object obj) { .... List<string> names = new List<string>(mo.GetDynamicMemberNames()); names.Sort(); if (names != null) { .... } .... } 

PVS-Studio 警告V3022表达式'names!= Null'始终为true。 DynamicDebuggerProxy.cs 426

我可能会忽略此警告以及V3022V3063诊断程序发出的许多类似警告。 有很多(很多)奇怪的支票,但这以某种方式渗入了我的灵魂。 在将带有null的局部变量名称与此变量进行比较之前,不仅可能会写入对新创建对象的引用,还会调用实例方法Sort 。 当然,这不是一个错误,但是就我而言,这个地方很有趣。

第8期

这是另一段有趣的代码。

 private static void InsertChildNoGrow(Symbol child) { .... while (sym?.nextSameName != null) { sym = sym.nextSameName; } Debug.Assert(sym != null && sym.nextSameName == null); sym.nextSameName = child; .... } 

PVS-Studio警告V3042可能为NullReferenceException。 '?。' 和“。” 运算符用于访问'sym'对象SymbolStore.cs的成员56

看看这里有什么。 满足以下两个条件之一时,循环结束:

  • sym == null ;
  • sym.nextSameName == null

第二个条件没有问题,因为第一个条件不能说,因为下面是对nextSameName实例字段的无条件调用,并且,如果symnull ,则在调用过程中将抛出NullReferenceException类型的异常。

“你瞎了吗? 还调用了Debug.Assert ,其中检查了sym!= Null “-有人可能反对。 但这全是盐! 在Debug.Assert的发行版中工作时,没有任何帮助,在上述状态下,我们得到的只是NullReferenceException 。 此外,在Microsoft- Roslyn的另一个项目中,我已经看到了类似的错误,其中Debug.Assert的情况非常相似。 罗斯林在您的允许下有点分心。

可以使用Microsoft.CodeAnalysis库重现该问题,也可以使用Syntax Visualizer直接在Visual Studio中重现该问题。 在Visual Studio版本16.1.6 +语法Visualizer 1.0上,此问题仍然存在。

要重现,下面的代码就足够了:

 class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } } 

接下来,在Syntax Visualizer中,您需要找到类型为ConstantPatternSyntax的语法树的节点,该节点对应于代码中的null ,并为其请求TypeSymbol

图片9

之后,Visual Studio将重新启动。 如果进入事件查看器,我们将在库中找到有关问题的信息:

 Application: devenv.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.Resources.MissingManifestResourceException at System.Resources.ManifestBasedResourceGroveler .HandleResourceStreamMissing(System.String) at System.Resources.ManifestBasedResourceGroveler.GrovelForResourceSet( System.Globalization.CultureInfo, System.Collections.Generic.Dictionary'2 <System.String,System.Resources.ResourceSet>, Boolean, Boolean, System.Threading.StackCrawlMark ByRef) at System.Resources.ResourceManager.InternalGetResourceSet( System.Globalization.CultureInfo, Boolean, Boolean, System.Threading.StackCrawlMark ByRef) at System.Resources.ResourceManager.InternalGetResourceSet( System.Globalization.CultureInfo, Boolean, Boolean) at System.Resources.ResourceManager.GetString(System.String, System.Globalization.CultureInfo) at Roslyn.SyntaxVisualizer.DgmlHelper.My. Resources.Resources.get_SyntaxNodeLabel() .... 

关于devenv.exe的问题:

 Faulting application name: devenv.exe, version: 16.1.29102.190, time stamp: 0x5d1c133b Faulting module name: KERNELBASE.dll, version: 10.0.18362.145, time stamp: 0xf5733ace Exception code: 0xe0434352 Fault offset: 0x001133d2 .... 

具有Roslyn库的调试版本,您可以找到发生异常的位置:

 private Conversion ClassifyImplicitBuiltInConversionSlow( TypeSymbol source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); if ( source.SpecialType == SpecialType.System_Void || destination.SpecialType == SpecialType.System_Void) { return Conversion.NoConversion; } .... } 

在这里,就像上面.NET Core库中的代码一样,也可以通过Debug.Assert进行检查,但是,在使用库的发行版时,它没有任何帮助。

第9期

分散注意力-就足够了,回到.NET Core库。 System.IO.IsolatedStorage程序包包含以下有趣的代码。

 private bool ContainsUnknownFiles(string directory) { .... return (files.Length > 2 || ( (!IsIdFile(files[0]) && !IsInfoFile(files[0]))) || (files.Length == 2 && !IsIdFile(files[1]) && !IsInfoFile(files[1])) ); } 

警告PVS-StudioV3088表达式用括号括起来两次:((表达式))。 不需要一对括号,否则会出现打印错误。 孤立的StorageFile.cs 839

说代码格式化令人迷惑不说。 简要浏览一下这段代码,我会说第一个运算符||的左操作数。 -files.Length> 2 ,右边的是括号中的那个。 至少代码是这样格式化的。 再仔细一点,您可以了解并非如此。 实际上,正确的操作数是((!IsIdFile(文件[0])&&!IsInfoFile(文件[0])))) 。 我认为,此代码非常令人困惑。

第10期

在PVS-Studio 7.03版本中,添加了诊断规则V3138,该规则可查找插补行中的错误。 更确切地说,在最有可能插值的行中,但是由于缺少$字符,所以它们不是。 System.Net库发现了对该诊断规则的一些有趣的响应。

 internal static void CacheCredential(SafeFreeCredentials newHandle) { try { .... } catch (Exception e) { if (!ExceptionCheck.IsFatal(e)) { NetEventSource.Fail(null, "Attempted to throw: {e}"); } } } 

PVS-Studio警告V3138字符串文字包含潜在的内插表达式。 考虑检查:e。 SSPIHandleCache.cs 42

Fail方法的第二个参数很可能应该是一个内插字符串,其中e异常的字符串表示形式将被替换为该字符串。 但是,由于缺少$字符,因此不会抛出该异常的字符串表示形式。

第11期

我遇到了另一个类似的案例。

 public static async Task<string> GetDigestTokenForCredential(....) { .... if (NetEventSource.IsEnabled) NetEventSource.Error(digestResponse, "Algorithm not supported: {algorithm}"); .... } 

PVS-Studio警告V3138字符串文字包含潜在的内插表达式。 考虑检查:算法。 AuthenticationHelper.Digest.cs 58

这种情况类似于上述情况,再次跳过$符号-错误的行转到Error方法。

第十二期

打包System.Net.Mail 。 该方法很小,我将全部介绍它,以便使错误更有趣。

 internal void SetContent(Stream stream) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (_streamSet) { _stream.Close(); _stream = null; _streamSet = false; } _stream = stream; _streamSet = true; _streamUsedOnce = false; TransferEncoding = TransferEncoding.Base64; } 

PVS-Studio警告V3008为 '_streamSet'变量连续两次分配值。 也许这是一个错误。 检查行:123、119。MimePart.cs 123

变量_streamSet的值的双重分配看起来很奇怪(首先-在条件下;然后-外部)。 将_stream变量清零的情况与此相同。 结果, _stream仍将设置为stream ,而_streamSettrue

第13期

分析器立即向System.Linq.Expressions库中的一个有趣位置发出了2条警告。 在这种情况下,它是一个功能而不是错误,但是,该方法还是非常有趣的……

 // throws NRE when o is null protected static void NullCheck(object o) { if (o == null) { o.GetType(); } } 

PVS-Studio警告

  • V3010需要使用函数“ GetType”的返回值。 Instruction.cs 36
  • V3080可能为空的取消引用。 考虑检查“ o”。 Instruction.cs 36

甚至没有什么可评论的。

图片20

第14期

让我们看看另一种情况,我们将“从外部”进行工作。 首先,我们将编写代码,确定问题,然后深入内部。 要研究,请使用System.Configuration.ConfigurationManager库和同名的NuGet包。 我使用的软件包版本为4.5.0。 我们将使用System.Configuration.CommaDelimitedStringCollection类。

让我们做一些不太棘手的事情。 例如,创建一个对象,提取其字符串表示形式,获取该字符串的长度并进行打印。 相关代码:

 CommaDelimitedStringCollection collection = new CommaDelimitedStringCollection(); Console.WriteLine(collection.ToString().Length); 

以防万一,请看一下ToString方法的描述:

图片11

没什么不寻常的,只是返回对象的字符串表示形式。 以防万一,我还要看看docs.microsoft.com-“ CommaDelimitedStringCollection.ToString方法 ”。 似乎也没什么特别的。

好了,运行代码执行,ii ...

图片12

嗯,出乎意料。 好吧,让我们尝试将元素添加到集合中,然后获取其字符串表示形式。 “完全偶然”,我们将添加一个空字符串:)。 代码将更改,如下所示:

 CommaDelimitedStringCollection collection = new CommaDelimitedStringCollection(); collection.Add(String.Empty); Console.WriteLine(collection.ToString().Length); 

我们启动它,看看...

图片13

又是什么?! 好了,让我们最后看一下CommaDelimitedStringCollection类的ToString方法的实现 。 代码如下所示:

 public override string ToString() { if (Count <= 0) return null; StringBuilder sb = new StringBuilder(); foreach (string str in this) { ThrowIfContainsDelimiter(str); // .... sb.Append(str.Trim()); sb.Append(','); } if (sb.Length > 0) sb.Length = sb.Length - 1; return sb.Length == 0 ? null : sb.ToString(); } 

PVS-Studio警告

  • V3108不建议从“ ToSting()”方法返回“ null”。 StringAttributeCollection.cs 57
  • V3108不建议从“ ToSting()”方法返回“ null”。 StringAttributeCollection.cs 71

在这里,我们看到两个地方,当前的ToString实现可以返回null 。 回想一下Microsoft在实现ToString方法时的建议,为此,我们再次转到docs.microsoft.com-“ Object.ToString Method ”:

对继承者的说明.... ToString()方法的重写应遵循以下准则:
  • ....
  • 您的ToString()重写不应返回Empty或 字符串。
  • ....

实际上,这就是PVS-Studio警告的地方。 上面我们写来重现问题的两个代码段到达了不同的退出点-分别返回null的第一和第二位。 挖得更深一些。

第一种情况。 Count-基类StringCollection的一个属性。 由于未添加任何元素, Count == 0 ,条件Count <= 0满足,返回null

在第二种情况下,我们为此使用实例方法CommaDelimitedStringCollection.Add添加了一个元素。

 public new void Add(string value) { ThrowIfReadOnly(); ThrowIfContainsDelimiter(value); _modified = true; base.Add(value.Trim()); } 

检入ThrowIf ...方法成功通过,并将该项目添加到基本集合中。 因此, Count的值等于1。现在返回ToString方法。 表达式Count <= 0的值为false ,因此,该方法不会退出并且代码将继续执行。 内部遍历开始,并将2个元素添加到StringBuilder中 -一个空字符串和一个逗号。 结果,发现sb仅包含一个逗号, Length属性的值分别为1。 表达式sb.Length> 0的值为true ,执行减法并写入sb.Length ,现在sb.Length的值为0。这导致该方法再次返回null

第十五期

出乎意料的是,我想使用System.Configuration.ConfigurationProperty类。 选择具有最多参数的构造函数:

 public ConfigurationProperty( string name, Type type, object defaultValue, TypeConverter typeConverter, ConfigurationValidatorBase validator, ConfigurationPropertyOptions options, string description); 

让我们看一下最后一个参数的描述:

 // description: // The description of the configuration entity. 

docs.microsoft.com上的构造函数说明也是如此。 好,让我们看一下在构造函数主体中如何使用此参数:

 public ConfigurationProperty(...., string description) { ConstructorInit(name, type, options, validator, typeConverter); SetDefaultValue(defaultValue); } 

并且不使用该参数。

PVS-Studio警告V3117未使用构造函数参数“ description”。 ConfigurationProperty.cs 62

他们可能没有故意使用它,但是相应参数的描述令人困惑。

第16期

我遇到了另一个类似的地方。 尝试自己查找错误,构造函数的代码如下。

 internal SectionXmlInfo( string configKey, string definitionConfigPath, string targetConfigPath, string subPath, string filename, int lineNumber, object streamVersion, string rawXml, string configSource, string configSourceStreamName, object configSourceStreamVersion, string protectionProviderName, OverrideModeSetting overrideMode, bool skipInChildApps) { ConfigKey = configKey; DefinitionConfigPath = definitionConfigPath; TargetConfigPath = targetConfigPath; SubPath = subPath; Filename = filename; LineNumber = lineNumber; StreamVersion = streamVersion; RawXml = rawXml; ConfigSource = configSource; ConfigSourceStreamName = configSourceStreamName; ProtectionProviderName = protectionProviderName; OverrideModeSetting = overrideMode; SkipInChildApps = skipInChildApps; } 

PVS-Studio警告V3117构造函数参数“ configSourceStreamVersion”未使用。SectionXmlInfo.cs 16

有一个对应的属性,尽管看起来有点奇怪:

 internal object ConfigSourceStreamVersion { set { } } 

通常,该代码看起来可疑。也许为了兼容性而保留了参数/属性,但这只是我的猜测。

第17期,

让我们看看在System.Runtime.WindowsRuntime.UI.Xaml和同名的NuGet包的代码中发现了什么有趣的东西
 public struct RepeatBehavior : IFormattable { .... public override string ToString() { return InternalToString(null, null); } .... } 

PVS-Studio警告V3108不建议从“ ToSting()”方法返回“ null”。 113个RepeatBehavior.cs

已经被通过熟悉的故事-一个方法的ToString可以返回值。因此,假设RepeatBehavior.ToString始终返回非零引用,那么调用代码的作者可能会在某些时候感到不愉快。同样,这与Microsoft准则有所不同。

当然,仅从此方法尚不清楚ToString是否可以返回null-您需要更深入地研究并查看InternalToString方法

 internal string InternalToString(string format, IFormatProvider formatProvider) { switch (_Type) { case RepeatBehaviorType.Forever: return "Forever"; case RepeatBehaviorType.Count: StringBuilder sb = new StringBuilder(); sb.AppendFormat( formatProvider, "{0:" + format + "}x", _Count); return sb.ToString(); case RepeatBehaviorType.Duration: return _Duration.ToString(); default: return null; } } 

分析器发现,如果在开关中执行默认分支,则InternalToString将返回null,因此null将返回ToString

RepeatBehavior是一个公共结构,而ToString是一个公共方法,因此您可以尝试在实践中重现该问题。为此,创建一个RepeatBehavior实例,在其上调用ToString方法,但请记住_Type不能等于RepeatBehaviorType.ForeverRepeatBehaviorType.CountRepeatBehaviorType.Duration

_Type是可以通过公共属性分配的私有字段:

 public struct RepeatBehavior : IFormattable { .... private RepeatBehaviorType _Type; .... public RepeatBehaviorType Type { get { return _Type; } set { _Type = value; } } .... } 

到目前为止,一切看起来都不错。继续,看看RepeatBehaviorType类型什么

 public enum RepeatBehaviorType { Count, Duration, Forever } 

如您所见,RepeatBehaviorType是包含所有三个元素的枚举。此外,所有这三个元素都包含在所需的switch表达式中但是,这并不意味着默认分支不可访问。

要重现此问题,请将System.Runtime.WindowsRuntime.UI.Xaml程序包(我使用的是版本4.3.0)连接到项目,然后执行以下代码。

 RepeatBehavior behavior = new RepeatBehavior() { Type = (RepeatBehaviorType)666 }; Console.WriteLine(behavior.ToString() is null); 

预计True将输出到控制台,这意味着ToString返回null,因为 _Type不等于case分支中的任何值,并且控制权传递给default分支实际上,我们追求的是。

我还想指出,在方法的注释或docs.microsoft.com上的注释中都没有指出该方法可以返回null

第18期

接下来,我们将研究System.Private.DataContractSerialization的一些警告

 private static class CharType { public const byte None = 0x00; public const byte FirstName = 0x01; public const byte Name = 0x02; public const byte Whitespace = 0x04; public const byte Text = 0x08; public const byte AttributeText = 0x10; public const byte SpecialWhitespace = 0x20; public const byte Comment = 0x40; } private static byte[] s_charType = new byte[256] { .... CharType.None, /* 9 (.) */ CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace| CharType.Text| CharType.SpecialWhitespace, /* A (.) */ CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace| CharType.Text| CharType.SpecialWhitespace, /* B (.) */ CharType.None, /* C (.) */ CharType.None, /* D (.) */ CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace, /* E (.) */ CharType.None, .... }; 

PVS-Studio警告

  • V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' 操作员。 XmlUTF8TextReader.cs 56
  • V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' 操作员。 XmlUTF8TextReader.cs 58
  • V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' 操作员。 XmlUTF8TextReader.cs 64

分析器发现CharType.Comment表达式的使用可疑。 CharType.Comment。自(CharType.Comment | CharType.Comment)== CharType.Comment以来,它看起来有点奇怪。初始化使用CharType.Comment的数组的其他元素时,没有这样的重复。

第19期

继续。让我们在方法说明中和在docs.microsoft.com 上查看有关XmlBinaryWriterSession.TryAdd方法的返回值的信息-“ XmlBinaryWriterSession.TryAdd(XmlDictionaryString,Int32)Method ”:返回:如果可以添加字符串,则为true;否则为false。否则为假。

现在让我们看一下该方法的主体:

 public virtual bool TryAdd(XmlDictionaryString value, out int key) { IntArray keys; if (value == null) throw System.Runtime .Serialization .DiagnosticUtility .ExceptionUtility .ThrowHelperArgumentNull(nameof(value)); if (_maps.TryGetValue(value.Dictionary, out keys)) { key = (keys[value.Key] - 1); if (key != -1) { // If the key is already set, then something is wrong throw System.Runtime .Serialization .DiagnosticUtility .ExceptionUtility .ThrowHelperError( new InvalidOperationException( SR.XmlKeyAlreadyExists)); } key = Add(value.Value); keys[value.Key] = (key + 1); return true; } key = Add(value.Value); keys = AddKeys(value.Dictionary, value.Key + 1); keys[value.Key] = (key + 1); return true; } 

PVS-Studio警告V3009奇怪的是,此方法始终返回一个相同的“ true”值。 XmlBinaryWriterSession.cs 29

该方法返回true或引发异常,但从不返回false这看起来很奇怪

第20期我

遇到了具有类似问题的代码,但现在却又相反了-它始终返回false

 internal virtual bool OnHandleReference(....) { if (xmlWriter.depth < depthToCheckCyclicReference) return false; if (canContainCyclicReference) { if (_byValObjectsInScope.Contains(obj)) throw ....; _byValObjectsInScope.Push(obj); } return false; } 

PVS-Studio警告V3009奇怪的是,此方法始终返回一个相同的'false'值。XmlObjectSerializerWriteContext.cs 415

因此,我们已经走了很长一段路!因此,在继续之前,我建议稍事休息-伸展肌肉,走一点,让眼睛休息,看着窗外...

图片21

我希望在这一点上您再次充满活力,所以让我们继续。 :)

第21期

让我们看一下System.Security.Cryptography.Algorithms项目中有趣的地方

 public override byte[] GenerateMask(byte[] rgbSeed, int cbReturn) { using (HashAlgorithm hasher = (HashAlgorithm)CryptoConfig.CreateFromName(_hashNameValue)) { byte[] rgbCounter = new byte[4]; byte[] rgbT = new byte[cbReturn]; uint counter = 0; for (int ib = 0; ib < rgbT.Length;) { // Increment counter -- up to 2^32 * sizeof(Hash) Helpers.ConvertIntToByteArray(counter++, rgbCounter); hasher.TransformBlock(rgbSeed, 0, rgbSeed.Length, rgbSeed, 0); hasher.TransformFinalBlock(rgbCounter, 0, 4); byte[] hash = hasher.Hash; hasher.Initialize(); Buffer.BlockCopy(hash, 0, rgbT, ib, Math.Min(rgbT.Length - ib, hash.Length)); ib += hasher.Hash.Length; } return rgbT; } } 

PVS-Studio警告V3080可能取消空引用。考虑检查“哈希”。 PKCS1MaskGenerationMethod.cs 37

分析器警告说,在评估hasher.TransformBlock表达式时,hasher变量可以为null,在这种情况下抛出NullReferenceException。由于进行了过程间分析,因此出现了此警告。因此,要了解这种情况下哈希是否可以为null,您需要转到CreateFromName方法



 public static object CreateFromName(string name) { return CreateFromName(name, null); } 

到目前为止,什么都没有-我们会更深入。具有两个参数CreateFromName重载版本的主体非常大,因此我引入了一个简化版本。

 public static object CreateFromName(string name, params object[] args) { .... if (retvalType == null) { return null; } .... if (cons == null) { return null; } .... if (candidates.Count == 0) { return null; } .... if (rci == null || typeof(Delegate).IsAssignableFrom(rci.DeclaringType)) { return null; } .... return retval; } 

如您所见,方法中有多个出口点,其中显式返回null因此,至少从理论上讲,在前面提到的向其发出警告的方法中,可能会发生NullReferenceException类型的异常

理论是好的,但让我们尝试在实践中重现问题。为此,请再次查看原始方法并注意要点。该方法的无关代码是可简化的。

 public class PKCS1MaskGenerationMethod : .... // <= 1 { .... public PKCS1MaskGenerationMethod() // <= 2 { _hashNameValue = DefaultHash; } .... public override byte[] GenerateMask(byte[] rgbSeed, int cbReturn) // <= 3 { using (HashAlgorithm hasher = (HashAlgorithm)CryptoConfig.CreateFromName(_hashNameValue)) // <= 4 { byte[] rgbCounter = new byte[4]; byte[] rgbT = new byte[cbReturn]; // <= 5 uint counter = 0; for (int ib = 0; ib < rgbT.Length;) // <= 6 { .... Helpers.ConvertIntToByteArray(counter++, rgbCounter); // <= 7 hasher.TransformBlock(rgbSeed, 0, rgbSeed.Length, rgbSeed, 0); .... } .... } } } 

让我们更详细地考虑关键点:

1,3。该类和方法具有公共访问修饰符。因此,连接库时此接口可用-您可以尝试重复该问题。

2。一个类(非抽象实例)具有一个公共构造函数,创建一个我们将使用的实例应该很容易。在某些情况下,我检查了班级是抽象的,因此要重复这个问题,我仍然必须寻找继承人和获取继承人的方法。

4CreateFromName不应引发任何异常,并且应返回null-最重要的一点,我们将在稍后返回。

5、6Cb返回应该大于0(当然,在成功创建数组的适当范围内)。要满足进一步的条件ib <rgbT.Length并进入循环体,必须满足条件cbReturn> 07Helpres.ConvertIntToByteArray应该可以正常工作。要满足依赖于方法参数的条件,只需传递足够的参数就足够了,例如:





  • rgbCeed-新字节[] {0,1,2,3};
  • cbReturn -42。

为了“破坏” CryptoConfig.CreateFromName方法,您必须能够更改_hashNameValue字段的值对我们来说幸运的是,它存在,因为在该字段的类中定义了包装器属性:
 public string HashName { get { return _hashNameValue; } set { _hashNameValue = value ?? DefaultHash; } } 

通过为HashName设置一个“ synthetic”值(更准确地说是_hashNameValue),可以在标记的第一个返回点上CreateFromName方法获得我将不讨论解析此方法的细节(希望您对此宽恕),因为它很长。结果,将引发类型为NullReferenceException的异常的代码可能如下所示:



 PKCS1MaskGenerationMethod tempObj = new PKCS1MaskGenerationMethod(); tempObj.HashName = "Dummy"; tempObj.GenerateMask(new byte[] { 1, 2, 3 }, 42); 

我们将调试库连接到项目,运行它并获得预期的结果:

图片10


为了引起兴趣,我尝试在NuGet软件包4.3.1版上执行相同的代码。

图片14


docs.microsoft.com上也没有描述有关生成的异常的信息,即输出参数对方法说明的限制-“ PKCS1MaskGenerationMethod.GenerateMask(字节[],Int32)方法 ”。

顺便说一下,在撰写本文和描述问题回放顺序的过程中,我发现了另外两种“破坏”该方法的方法:

  • 太大的值作为参数传递给cbReturn ;
  • 通过如rgbSeed

在第一种情况下,我们获得OutOfMemoryException类型的异常

图片15

在第二种情况下,当执行rgbSeed.Length表达式时,我们将获得NullReferenceException类型的异常在这种情况下,hasher必须具有非零值非常重要,否则执行线程将不会达到rgbSeed.Length第22期遇到了更多类似的地方。





 public class SignatureDescription { .... public string FormatterAlgorithm { get; set; } public string DeformatterAlgorithm { get; set; } public SignatureDescription() { } .... public virtual AsymmetricSignatureDeformatter CreateDeformatter( AsymmetricAlgorithm key) { AsymmetricSignatureDeformatter item = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName(DeformatterAlgorithm); item.SetKey(key); // <= return item; } public virtual AsymmetricSignatureFormatter CreateFormatter( AsymmetricAlgorithm key) { AsymmetricSignatureFormatter item = (AsymmetricSignatureFormatter) CryptoConfig.CreateFromName(FormatterAlgorithm); item.SetKey(key); // <= return item; } .... } 

PVS-Studio警告

  • V3080可能为空的取消引用。考虑检查“项目”。SignatureDescription.cs 31
  • V3080可能为空的取消引用。考虑检查“项目”。签名说明.cs 38

同样,我们可以将写入FormatterAlgorithmDeformatterAlgorithm属性,对于CryptoConfig.CreateFromName方法,在CreateDeformatterCreateFormatter方法中,该属性将返回null。接下来,当调用实例方法SetKey时,抛出NullReferenceException。同样,该问题在实践中很容易重现:

 SignatureDescription signature = new SignatureDescription() { DeformatterAlgorithm = "Dummy", FormatterAlgorithm = "Dummy" }; signature.CreateDeformatter(null); // NRE signature.CreateFormatter(null); // NRE 

在这种情况下,当调用CreateDeformatter和调用CreateFormatter时,将抛出NullReferenceException类型的异常

第23期

让我们看一下System.Private.Xml项目中有趣的地方

 public override void WriteBase64(byte[] buffer, int index, int count) { if (!_inAttr && (_inCDataSection || StartCDataSection())) _wrapped.WriteBase64(buffer, index, count); else _wrapped.WriteBase64(buffer, index, count); } 

PVS-Studio警告V3004'then '语句等效于'else'语句。QueryOutputWriterV1.cs 242 if语句

thenelse分支包含相同的代码看起来很奇怪可能是一个错误,并且其中一个分支应该执行其他操作,或者可以省略if语句第24期



 internal void Depends(XmlSchemaObject item, ArrayList refs) { .... if (content is XmlSchemaSimpleTypeRestriction) { baseType = ((XmlSchemaSimpleTypeRestriction)content).BaseType; baseName = ((XmlSchemaSimpleTypeRestriction)content).BaseTypeName; } else if (content is XmlSchemaSimpleTypeList) { .... } else if (content is XmlSchemaSimpleTypeRestriction) { baseName = ((XmlSchemaSimpleTypeRestriction)content).BaseTypeName; } else if (t == typeof(XmlSchemaSimpleTypeUnion)) { .... } .... } 

PVS-Studio 警告V3003检测到使用'if(A){...} else if(A){...}'模式。存在逻辑错误的可能性。检查行:381,396。ImportContext.cs 381 if-else-if

序列具有两个相同的条件表达式- 内容为XmlSchemaSimpleTypeRestriction更有趣的是,相应运算符的then分支主体包含一组不同的表达式。一种或另一种方式,将执行第一个对应的then分支的主体(如果条件表达式为true),或者如果对应的表达式为false,则不执行任何操作。第25期



为了使在以下方法中查找错误更加有趣,我将他的整个身体带了起来。

 public bool MatchesXmlType(IList<XPathItem> seq, int indexType) { XmlQueryType typBase = GetXmlType(indexType); XmlQueryCardinality card; switch (seq.Count) { case 0: card = XmlQueryCardinality.Zero; break; case 1: card = XmlQueryCardinality.One; break; default: card = XmlQueryCardinality.More; break; } if (!(card <= typBase.Cardinality)) return false; typBase = typBase.Prime; for (int i = 0; i < seq.Count; i++) { if (!CreateXmlType(seq[0]).IsSubtypeOf(typBase)) return false; } return true; } 

如果您做到了,那就恭喜!
如果不是,PVS-Studio警告将帮助您:V3102通过循环内的常量索引可疑访问“ seq”对象的元素。 XmlQueryRuntime.cs 738 使用表达式i <seq.Count作为退出条件,

执行for循环。建议他们要绕过seq的序列。仅在循环中,它们不使用计数器(seq [i])而是使用数字文字-零(seq [0]访问序列的元素问题26下面的错误仅适用于一小段代码,但因此也很有趣。





 public override void WriteValue(string value) { WriteValue(value); } 

警告工作室PVS的V3110的可能无限递归内“WriteValue”的方法。 XmlAttributeCache.cs 166

该方法调用自身,从而形成没有退出条件的递归。

第27期

 public IList<XPathNavigator> DocOrderDistinct(IList<XPathNavigator> seq) { if (seq.Count <= 1) return seq; XmlQueryNodeSequence nodeSeq = (XmlQueryNodeSequence)seq; if (nodeSeq == null) nodeSeq = new XmlQueryNodeSequence(seq); return nodeSeq.DocOrderDistinct(_docOrderCmp); } 

PVS-Studio警告V3095在对null进行验证之前,已使用'seq'对象。检查行:880、884。XmlQueryRuntime.cs 880

作为参数,该方法可能会收到一个,这就是为什么在调用Count属性时抛出NullReferenceException类型的异常的原因。下面我们检查由于显式转换seq获得nodeSeq变量,但是为什么不很清楚。如果seqnull,则由于异常,线程将无法进行此检查。如果seq的值不为null,则:

  • 如果强制类型转换失败,抛出InvalidCastException类型的异常
  • 如果强制转换成功,则nodeSeq肯定具有非null的值

第28期

遇到了4个没有使用参数的设计器。可能出于兼容性考虑而保留它们,但我尚未找到有关这些未使用参数的任何注释。

PVS-Studio警告

  • V3117未使用构造函数参数“ securityUrl”。XmlSecureResolver.cs 15
  • V3117未使用构造函数参数“ strdata”。XmlEntity.cs 18
  • V3117不使用构造函数参数“位置”。编译.cs 58
  • V3117不使用构造函数参数“ access”。XmlSerializationILGen.cs 38

我对第一个感兴趣(至少我将它写到了这篇文章的警告列表中)。 什么啊不知道 也许是名字。

 public XmlSecureResolver(XmlResolver resolver, string securityUrl) { _resolver = resolver; } 

为了感兴趣,我去看看他们在docs.microsoft.com上写的关于securityUrl参数的XmlSecureResolver构造函数用于创建将应用于基础XmlResolver的PermissionSet的URL。 XmlSecureResolver在基础XmlResolver上调用GetEntity(Uri,String,Type)之前,在创建的PermissionSet上调用PermitOnly()。问题29System.Private.Uri程序包中我发现一种方法与Microsoft重写ToString方法的准则几乎没有冲突。回忆一下Object.ToString方法页面上的提示之一您的ToString()重写不应引发异常







被覆盖的方法本身看起来像这样:

 public override string ToString() { if (_username.Length == 0 && _password.Length > 0) { throw new UriFormatException(SR.net_uri_BadUserPassword); } .... } 

PVS-Studio警告V3108不建议从“ ToSting()”方法引发异常。UriBuilder.cs 406

通过公共属性UserNamePassword分别_username字段设置空字符串和_password字段设置非空行,然后调用ToString方法的代码将获得异常。此类代码的示例:

 UriBuilder uriBuilder = new UriBuilder() { UserName = String.Empty, Password = "Dummy" }; String stringRepresentation = uriBuilder.ToString(); Console.WriteLine(stringRepresentation); 

但是在这种情况下,开发人员会诚实地警告称,在调用时可能会引发异常-在方法和docs.microsoft.com的注释“ UriBuilder.ToString方法中均对此进行了描述

第30期

看一下在System.Data.Common项目代码上发出的​​警告

 private ArrayList _tables; private DataTable GetTable(string tableName, string ns) { .... if (_tables.Count == 0) return (DataTable)_tables[0]; .... } 

PVS-Studio警告V3106可能索引超出范围。索引“ 0”指向“ _tables”界限之外。XMLDiffLoader.cs 277

这段代码看起来异常吗?您认为这是什么?想办法抛出ArgumentOutOfRangeException类型的异常吗?我可能不会对此方法感到惊讶。通常,该代码是奇怪且可疑的。

第31期

 internal XmlNodeOrder ComparePosition(XPathNodePointer other) { RealFoliate(); other.RealFoliate(); Debug.Assert(other != null); .... } 

PVS-Studio警告V3095在对null进行验证之前,已使用“其他”对象。检查线路:1095,1096 XPathNodePointer.cs 1095

表达其他!= NULL作为参数Debug.Assert的明确暗示,作为参数ComparePosition可以得到的值无效。至少,他们想抓住这种情况。但同时,另一行上方的行调用实例方法RealFoliate。结果,如果othernull,则将抛出NullReferenceException类型的异常。在通过Assert检查之前

第32期
 private PropertyDescriptorCollection GetProperties(Attribute[] attributes) { .... foreach (Attribute attribute in attributes) { Attribute attr = property.Attributes[attribute.GetType()]; if ( (attr == null && !attribute.IsDefaultAttribute()) || !attr.Match(attribute)) { match = false; break; } } .... } 

PVS-Studio警告V3080可能取消空引用。考虑检查“ attr”。DbConnectionStringBuilder.cs 534 if语句

条件表达式看起来有点可疑。Match是一个实例方法。通过检查attr == null来判断null是此变量的有效(预期)值。因此,如果执行线程到达了运算符||的右操作数。如果attrnull,我们将获得NullReferenceException类型的异常因此,例外情况如下:



  1. attrnull . &&.
  2. !attribute.IsDefaultAttribute()false . && — false .
  3. || false , .
  4. attrnull , Match .

Issue 33

 private int ReadOldRowData( DataSet ds, ref DataTable table, ref int pos, XmlReader row) { .... if (table == null) { row.Skip(); // need to skip this element if we dont know about it, // before returning -1 return -1; } .... if (table == null) throw ExceptionBuilder.DiffgramMissingTable( XmlConvert.DecodeName(row.LocalName)); .... } 

PVS-Studio警告V3021有两个带有相同条件表达式的'if'语句。第一个'if'语句包含方法return。这意味着第二个“ if”语句是毫无意义的XMLDiffLoader.cs301。

有两个if语句包含相同的条件表达式-table == null。此外,这些运算符的分支包含不同的动作-在一种情况下,该方法以值-1退出,在第二种情况下,引发异常。在两次检查之间,变量不会更改。因此,不会抛出有问题的异常。

第34期

看一下System.ComponentModel.TypeConverter项目中的一个有趣方法。更准确地说,首先看一下描述它的注释:

从格式化的字符串中删除最后一个字符。 (删除虚拟字符串中的最后一个字符)。退出时,out参数包含实际执行操作的位置。此位置相对于测试字符串。 MaskedTextResultHint输出参数可提供有关操作结果的更多信息。 成功返回true 否则返回false

返回值的关键:如果操作成功,则该方法返回true,否则返回false。让我们看看实际上发生了什么。

 public bool Remove(out int testPosition, out MaskedTextResultHint resultHint) { .... if (lastAssignedPos == INVALID_INDEX) { .... return true; // nothing to remove. } .... return true; } 

PVS-Studio警告V3009奇怪的是,此方法始终返回一个相同的“ true”值。 MaskedTextProvider.cs 1529

实际上,事实证明该方法的唯一返回值为true

第35期

 public void Clear() { if (_table != null) { .... } if (_table.fInitInProgress && _delayLoadingConstraints != null) { .... } .... } 

PVS-Studio警告V3125在验证为空后使用了'_table'对象。检查行:437,423. ConstraintCollection.cs 437

检查_table!= Null代表自己-_table变量可以为null。至少在这种情况下,需要重新保险。但是,它们下面通过_table访问实例字段,而不检查null - _table .fInitInProgress

第36期

现在,让我们看看在System.Runtime.Serialization.Formatters项目的项目代码上发出的​​一些警告

 private void Write(....) { .... if (memberNameInfo != null) { .... _serWriter.WriteObjectEnd(memberNameInfo, typeNameInfo); } else if ((objectInfo._objectId == _topId) && (_topName != null)) { _serWriter.WriteObjectEnd(topNameInfo, typeNameInfo); .... } else if (!ReferenceEquals(objectInfo._objectType, Converter.s_typeofString)) { _serWriter.WriteObjectEnd(typeNameInfo, typeNameInfo); } } 

PVS-Studio警告V3038参数多次传递给方法。可能应改为传递其他参数。BinaryObjectWriter.cs 262

分析仪不好意思最后一次通话_serWriter.WriteObjectEnd有两个相同的参数- typeNameInfo这似乎是一个错字,但肯定不能说。我决定看看所谓的WriteObjectEnd方法什么

 internal void WriteObjectEnd(NameInfo memberNameInfo, NameInfo typeNameInfo) { } 

好吧...继续前进。 :)

第37期

 internal void WriteSerializationHeader( int topId, int headerId, int minorVersion, int majorVersion) { var record = new SerializationHeaderRecord( BinaryHeaderEnum.SerializedStreamHeader, topId, headerId, minorVersion, majorVersion); record.Write(this); } 

查看此代码,您不会立即在这里说错或看起来可疑。但是分析仪可能会说它已经警告了他。

警告PVS-StudioV3066传递给'SerializationHeaderRecord'构造函数的参数的可能错误顺序:'minorVersion'和'majorVersion'。 BinaryFormatterWriter.cs 111

让我们看一下被称为的SerializationHeaderRecord类的构造函数

 internal SerializationHeaderRecord( BinaryHeaderEnum binaryHeaderEnum, int topId, int headerId, int majorVersion, int minorVersion) { _binaryHeaderEnum = binaryHeaderEnum; _topId = topId; _headerId = headerId; _majorVersion = majorVersion; _minorVersion = minorVersion; } 

如您所见,构造函数参数的顺序为majorVersionminorVersion;调用构造函数时,它们以minorVersionmajorVersion的顺序传输。听起来像错字。如果这是计划的(突然之间?)-我认为应该留下解释性意见。

第38期

 internal ObjectManager( ISurrogateSelector selector, StreamingContext context, bool checkSecurity, bool isCrossAppDomain) { _objects = new ObjectHolder[DefaultInitialSize]; _selector = selector; _context = context; _isCrossAppDomain = isCrossAppDomain; } 

PVS-Studio警告V3117未使用构造函数参数'checkSecurity'。ObjectManager.cs 33 不会以任何方式使用checkSecurity

的构造函数参数尚无评论。我将假定它是为了兼容而保留的,但是在最近几年有关安全性的讨论中,这看起来还是很有趣的。第39期遇到了另一个代码,这对我来说似乎很不寻常。在检测到的所有三种情况下,该模式看起来都是1比1,并且在具有相同名称和变量名称的方法中找到。因此:





  • 我要么没有足够的开悟,也不了解这种重复的含义;
  • 使用复制粘贴方法通过代码传播的错误。

代码本身:

 private void EnlargeArray() { int newLength = _values.Length * 2; if (newLength < 0) { if (newLength == int.MaxValue) { throw new SerializationException(SR.Serialization_TooManyElements); } newLength = int.MaxValue; } FixupHolder[] temp = new FixupHolder[newLength]; Array.Copy(_values, 0, temp, 0, _count); _values = temp; } 

PVS-Studio警告

  • V3022表达式'newLength == int.MaxValue'始终为false。ObjectManager.cs 1423
  • V3022表达式'newLength == int.MaxValue'始终为false。ObjectManager.cs 1511
  • V3022表达式'newLength == int.MaxValue'始终为false。对象管理器.cs 1558

其他方法的不同之处在于临时数组的元素类型(不是FixupHolder,而是longobject)。所以我仍然对复制粘贴有所怀疑... System.Data.Odbc项目中的

第40版

代码

 public string UnquoteIdentifier(....) { .... if (!string.IsNullOrEmpty(quotePrefix) || quotePrefix != " ") { .... } .... } 

PVS-Studio警告V3022表达式'!String.IsNullOrEmpty(quotePrefix)|| quotePrefix!=“”'始终为true。 OdbcCommandBuilder.cs 338

分析器认为上述表达式始终为true。确实是。实际上,quotePrefix包含什么值都没有关系-条件本身的写法不正确。让我们做对。

我们有运营商||,因此,表达式的值是,如果左边或右边(或两者)将操作数。左边的一切都很清楚。仅当左侧操作数为false时,才对右侧操作数进行求值因此,如果表达式的组成使得右操作数的值始终为true,则当左值的值为false时,整个表达式的结果将始终为true

从上面的代码中我们知道,如果计算出正确的操作数,则字符串的值。IsNullOrEmpty(quotePrefix)表达式true,因此,其中一条语句true

  • quotePrefix == null ;
  • quotePrefix.Length == 0

如果这些语句中的任何一个为true,则表达式quotePrefix!=“”也将为true ,我们想证明这一点。因此,无论quotePrefix的内容如何,整个表达式的值始终为true第41期返回有关带有未使用参数的构造函数的讨论:





 private sealed class PendingGetConnection { public PendingGetConnection( long dueTime, DbConnection owner, TaskCompletionSource<DbConnectionInternal> completion, DbConnectionOptions userOptions) { DueTime = dueTime; Owner = owner; Completion = completion; } public long DueTime { get; private set; } public DbConnection Owner { get; private set; } public TaskCompletionSource<DbConnectionInternal> Completion { get; private set; } public DbConnectionOptions UserOptions { get; private set; } } 

PVS-Studio 警告V3117未使用构造函数参数“ userOptions”。DbConnectionPool.cs 26

从分析器代码和警告中可以明显看出,仅使用了一个构造函数参数— userOptions,而其他参数用于初始化相同名称的属性。好像他们忘记了初始化属性之一。

问题42

在此项目中有两次遇到可疑代码。模式是相同的。

 private DataTable ExecuteCommand(....) { .... foreach (DataRow row in schemaTable.Rows) { resultTable.Columns .Add(row["ColumnName"] as string, (Type)row["DataType"] as Type); } .... } 

PVS-Studio警告

  • V3051类型转换过多。该对象已经是“类型”类型。dbMetaDataFactory.cs 176
  • V3051类型转换过多。该对象已经是“类型”类型。OdbcMetaDataFactory.cs 1109

作为Type的表达式(Type)行[“ DataType”]看起来可疑首先将执行显式转换,然后通过as运算符进行转换如果row [“ DataType”]的值为null,则它将成功通过两个强制转换,并作为Add方法的参数如果row [“ DataType”]返回的值不能转换为Type,则显式转换将抛出InvalidCastException最后,为什么会有两个鬼?问题是开放的。

发行第43期

让我们看一下System.Runtime.InteropServices.RuntimeInformation中的可疑代码段

 public static string FrameworkDescription { get { if (s_frameworkDescription == null) { string versionString = (string)AppContext.GetData("FX_PRODUCT_VERSION"); if (versionString == null) { .... versionString = typeof(object).Assembly .GetCustomAttribute< AssemblyInformationalVersionAttribute>() ?.InformationalVersion; .... int plusIndex = versionString.IndexOf('+'); .... } .... } .... } } 

PVS-Studio警告V3105通过空条件运算符分配了'versionString'变量后,即可使用。 NullReferenceException是可能的。 RuntimeInformation.cs 29 versionString变量调用IndexOf方法时,

分析器会警告NullReferenceException类型的可能异常。检索变量的值时,代码作者在访问InfromationalVersion属性时使用'?。'运算符不会获取NullReferenceException。笑话是,如果对GetCustomAttribute <...>的调用返回null,仍然会引发异常,但是会更低-调用IndexOf方法时,因为versionString将为null

发行第44期

我们转到System.ComponentModel.Composition项目,并看到一些警告。立即针对以下代码发出2条警告:

 public static bool CanSpecialize(....) { .... object[] genericParameterConstraints = ....; GenericParameterAttributes[] genericParameterAttributes = ....; // if no constraints and attributes been specifed, anything can be created if ((genericParameterConstraints == null) && (genericParameterAttributes == null)) { return true; } if ((genericParameterConstraints != null) && (genericParameterConstraints.Length != partArity)) { return false; } if ((genericParameterAttributes != null) && (genericParameterAttributes.Length != partArity)) { return false; } for (int i = 0; i < partArity; i++) { if (!GenericServices.CanSpecialize( specialization[i], (genericParameterConstraints[i] as Type[]). CreateTypeSpecializations(specialization), genericParameterAttributes[i])) { return false; } } return true; } 

PVS-Studio警告

  • V3125在对null进行验证之后,使用了'genericParameterConstraints'对象。检查行:603、589。GenericSpecializationPartCreationInfo.cs 603
  • V3125在对null进行验证后,使用了'genericParameterAttributes'对象。检查行:604、594。GenericSpecializationPartCreationInfo.cs 604

代码中有genericParameterAttributes!= NullgenericParameterConstraints!= Null检查。因此, -这些变量考虑允许值。如果两个变量都为null,则退出方法-没有问题。但是,如果任何变量为null,但方法未退出怎么办?如果这样的状态是可能的,并且执行到达循环,我们将得到NullReferenceException类型的异常

第45期

让我们看一下来自同一项目的另一个有趣的警告。而且,尽管我们做不同的事情-首先我们再次使用该类,然后查看代码。我们会将最新可用的预发行版本的NuGet软件包连接到项目(我安装了4.6.0-preview6.19303.8软件包)。让我们编写一个简单的代码,例如:

 LazyMemberInfo lazyMemberInfo = new LazyMemberInfo(); var eq = lazyMemberInfo.Equals(null); Console.WriteLine(eq); 

在docs.microsoft.com上未对Equals方法进行评论,我没有为.NET Core(仅针对.NET Framework)找到此方法的描述。如果您查看它(“ LazyMemberInfo.Equals(Object)方法 ”)-看不到任何异常-它返回truefalse,没有关于生成的异常的信息。

我们运行代码以执行,请参见:

图片16

我们可以稍作改动,编写以下代码,并得出一个有趣的结论:

 LazyMemberInfo lazyMemberInfo = new LazyMemberInfo(); var eq = lazyMemberInfo.Equals(typeof(String)); Console.WriteLine(eq); 

代码执行的结果。

图片17

有趣的是,这两个异常都是在同一表达式中生成的。让我们看一下Equals方法

 public override bool Equals(object obj) { LazyMemberInfo that = (LazyMemberInfo)obj; // Difefrent member types mean different members if (_memberType != that._memberType) { return false; } // if any of the lazy memebers create accessors in a delay-loaded fashion, // we simply compare the creators if ((_accessorsCreator != null) || (that._accessorsCreator != null)) { return object.Equals(_accessorsCreator, that._accessorsCreator); } // we are dealing with explicitly passed accessors in both cases if(_accessors == null || that._accessors == null) { throw new Exception(SR.Diagnostic_InternalExceptionMessage); } return _accessors.SequenceEqual(that._accessors); } 

PVS-Studio 警告V3115将 “ null”传递给“ Equals”方法不应导致“ NullReferenceException”。 LazyMemberInfo.cs 116

实际上,在此分析器稍作摇摆,因为它对表达式that._memberType发出了警告。但是,异常在上面-执行表达式(LazyMemberInfo)obj时发生。已经记录在案。

使用InvalidCastException,我认为一切都清楚了。为什么会引发NullReferenceException?事实是LazyMemberInfo是一个结构;因此,将执行解压缩。解压缩null只会导致type的异常NullReferenceException好吧,这也是注释中的一些错别字,也可以同时修正。好吧,作者的良知仍然明确地抛出了例外。

第四十六期 TriState结构的System.Drawing.Common

发现了类似的内容

 public override bool Equals(object o) { TriState state = (TriState)o; return _value == state._value; } 

PVS-Studio 警告V3115将 “ null”传递给“ Equals”方法不应导致“ NullReferenceException”。TriState.cs 53

问题与上述情况相同。

第47期

考虑System.Text.Json的一些摘要

记住,我们说过ToString不应该返回null系好

 public override string ToString() { switch (TokenType) { case JsonTokenType.None: case JsonTokenType.Null: return string.Empty; case JsonTokenType.True: return bool.TrueString; case JsonTokenType.False: return bool.FalseString; case JsonTokenType.Number: case JsonTokenType.StartArray: case JsonTokenType.StartObject: { // null parent should have hit the None case Debug.Assert(_parent != null); return _parent.GetRawValueAsString(_idx); } case JsonTokenType.String: return GetString(); case JsonTokenType.Comment: case JsonTokenType.EndArray: case JsonTokenType.EndObject: default: Debug.Fail($"No handler for {nameof(JsonTokenType)}.{TokenType}"); return string.Empty; } } 

乍一看,此方法为null并且不会返回,但分析器主张相反。

PVS-Studio警告V3108不建议从“ ToSting()”方法返回“ null”。JsonElement.cs 1460

分析器指向调用GetString()方法的一行让我们看一下:

 public string GetString() { CheckValidInstance(); return _parent.GetString(_idx, JsonTokenType.String); } 

深入探讨-进入GetString方法的重载版本
 internal string GetString(int index, JsonTokenType expectedType) { .... if (tokenType == JsonTokenType.Null) { return null; } .... } 

在这里,我们已经看到了从该方法以及最初考虑的ToString返回null的条件第48期另一个有趣的地方:





 internal JsonPropertyInfo CreatePolymorphicProperty(....) { JsonPropertyInfo runtimeProperty = CreateProperty(property.DeclaredPropertyType, runtimePropertyType, property.ImplementedPropertyType, property?.PropertyInfo, Type, options); property.CopyRuntimeSettingsTo(runtimeProperty); return runtimeProperty; } 

PVS-Studio警告V3042可能为NullReferenceException。 '?。'和“。”运算符用于访问“属性”对象JsonClassInfo.AddProperty.cs的成员。179

在调用CreateProperty方法时,它们通过变量property多次访问属性property.DeclaredPropertyTypeproperty.ImplementedPropertyTypeproperty?.PropertyInfo。如您所见,在一种情况下,它们使用“?。”运算符。如果此处不是多余的,并且property可以为null,则此操作符将无济于事,因为在直接访问时将生成该类型的异常NullReferenceException

第49期

,在System.Security.Cryptography.Xml项目中发现以下可疑位置,它们成对出现,其他警告也多次发生。代码看起来又像复制粘贴,自己比较。

第一个片段:

 public void Write(StringBuilder strBuilder, DocPosition docPos, AncestralNamespaceContextManager anc) { docPos = DocPosition.BeforeRootElement; foreach (XmlNode childNode in ChildNodes) { if (childNode.NodeType == XmlNodeType.Element) { CanonicalizationDispatcher.Write( childNode, strBuilder, DocPosition.InRootElement, anc); docPos = DocPosition.AfterRootElement; } else { CanonicalizationDispatcher.Write(childNode, strBuilder, docPos, anc); } } } 

第二段:

 public void WriteHash(HashAlgorithm hash, DocPosition docPos, AncestralNamespaceContextManager anc) { docPos = DocPosition.BeforeRootElement; foreach (XmlNode childNode in ChildNodes) { if (childNode.NodeType == XmlNodeType.Element) { CanonicalizationDispatcher.WriteHash( childNode, hash, DocPosition.InRootElement, anc); docPos = DocPosition.AfterRootElement; } else { CanonicalizationDispatcher.WriteHash(childNode, hash, docPos, anc); } } } 

PVS-Studio警告

  • V3061参数'docPos'总是在使用前在方法主体中重写。CanonicalXmlDocument.cs 37
  • V3061参数'docPos'总是在使用前在方法主体中重写。CanonicalXmlDocument.cs 54

在这两种方法中,docPos参数在使用其值之前都会被覆盖,因此,用作该方法参数的值将被简单地忽略。

第50期

考虑有关System.Data.SqlClient项目代码的一些警告

 private bool IsBOMNeeded(MetaType type, object value) { if (type.NullableType == TdsEnums.SQLXMLTYPE) { Type currentType = value.GetType(); if (currentType == typeof(SqlString)) { if (!((SqlString)value).IsNull && ((((SqlString)value).Value).Length > 0)) { if ((((SqlString)value).Value[0] & 0xff) != 0xff) return true; } } else if ((currentType == typeof(string)) && (((String)value).Length > 0)) { if ((value != null) && (((string)value)[0] & 0xff) != 0xff) return true; } else if (currentType == typeof(SqlXml)) { if (!((SqlXml)value).IsNull) return true; } else if (currentType == typeof(XmlDataFeed)) { return true; // Values will eventually converted to unicode string here } } return false; } 

PVS-Studio警告V3095在对空值进行验证之前,已使用“值”对象。检查行:8696,8708。TdsParser.cs 8696

分析器被检查弄糊涂了!=在其中一种情况下为Null。似乎她在重构期间迷路了,因为多次取消引用上述。如果value可以为null,则表示情况不好。

第51期

测试中的下一个错误,但对我来说似乎很有趣,因此我决定提出。

 protected virtual TDSMessageCollection CreateQueryResponse(....) { .... if (....) { .... } else if ( lowerBatchText.Contains("name") && lowerBatchText.Contains("state") && lowerBatchText.Contains("databases") && lowerBatchText.Contains("db_name")) // SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name() { // Delegate to current database response responseMessage = _PrepareDatabaseResponse(session); } .... } 

警告PVS-StudioV3053表达式过多。检查子字符串“名称”和“ db_name”。 QueryEngine.cs 151

实际上,在这种情况下,lowerBatchText.Contains(“名称”)lowerBatchText.Contains(“ db_name”)子表达式的组合是多余的。实际上,如果要检查的字符串包含子字符串“ db_name”,那么它还将包含子字符串“ name”。如果字符串不包含“ name”,那么它也将不包含“ db_name”。事实证明,检查lowerBatchText.Contains(“ name”)是多余的。如果所检查的字符串不包含“ name”,则除非它可以减少所评估表达式的数量,否则它会被禁用

第52期 System.Net.Requests

项目中的可疑代码段

 protected override PipelineInstruction PipelineCallback( PipelineEntry entry, ResponseDescription response, ....) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Command:{entry?.Command} Description:{response?.StatusDescription}"); // null response is not expected if (response == null) return PipelineInstruction.Abort; .... if (entry.Command == "OPTS utf8 on\r\n") .... .... } 

PVS-Studio警告V3125在对null进行验证后,使用了“ entry”对象。检查行:270,227。FtpControlStream.cs 270

编译内插行时,使用表达式条目?。命令响应?。代替'。'运算符如果任何对应的参数为null,则使用'?。'不会获得NullReferenceException类型的异常。在这种情况下,此技术有效。此外,从代码中可以看到响应的可能的切除(如果response == null则从方法中退出),但是用于输入没什么 结果,如果entrynull在调用entry.Command将引发异常(已经使用'。' 不是'?。')。

接下来,我们将再次对代码进行相当详细的研究,因此我建议您再休息一会儿-分心,冲泡茶或煮咖啡,之后我很高兴再次在这里见到您。

图片23

你回来了吗 然后,让我们继续。 :)

第53期

现在让我们看一下System.Collections.Immutable项目中的一些有趣内容这次,让我们对System.Collections.Immutable.ImmutableArray <T>结构进行一些实验我们对方法IStructuralEquatable.EqualsIStructuralComparable.CompareTo感兴趣

让我们从IStructuralEquatable.Equals方法开始下面给出了代码,我建议尝试自行了解它的问题:

 bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { var self = this; Array otherArray = other as Array; if (otherArray == null) { var theirs = other as IImmutableArray; if (theirs != null) { otherArray = theirs.Array; if (self.array == null && otherArray == null) { return true; } else if (self.array == null) { return false; } } } IStructuralEquatable ours = self.array; return ours.Equals(otherArray, comparer); } 

你成功了吗?如果是这样,再次表示祝贺。 :)

PVS-Studio警告V3125在针对空值验证了“我们的”对象之后,使用了该对象。检查行:1212,1204。ImmutableArray_1.cs 1212

分析器通过位于最后一个return语句中ours变量混淆了实例Equals方法的调用,因为它假定此处可能存在NullReferenceException类型的异常。他是如何得出这个结论的?为了便于解释,下面是同一方法的简化代码。

 bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { .... if (....) { .... if (....) { .... if (self.array == null && otherArray == null) { .... } else if (self.array == null) { .... } } } IStructuralEquatable ours = self.array; return ours.Equals(otherArray, comparer); } 

根据最新的表述,我们看到的价值我们是从获得self.array。上面,self.array == null检查执行了几次。因此,self.array以及我们的self.array可以为null。至少在理论上。在实践中是否可以实现这种条件?让我们尝试找出答案。为此,我再次将方法的主体与关键点分开。

 bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { var self = this; // <= 1 Array otherArray = other as Array; if (otherArray == null) // <= 2 { var theirs = other as IImmutableArray; if (theirs != null) // <= 3 { otherArray = theirs.Array; if (self.array == null && otherArray == null) { return true; } else if (self.array == null) // <= 4 { return false; } } IStructuralEquatable ours = self.array; // <= 5 return ours.Equals(otherArray, comparer); } 

要点1self.array == this.array(由于self = this)。因此,在调用该方法之前,必须达到状态this.array == null

重点2。我们可以忽略if,这将是实现目标或进入内部的最简单选择。要忽略这种情况,变量other的类型必须是Array类型或它的派生类型,而other不为null 足够了。然后otherArray中使用as运算符将写入非零引用,并且我们将忽略第一个if语句

重点3这一点意味着一条更复杂的道路。我们绝对需要在第二条if语句(具有条件表达式“ 他们的!”的那条语句)中退出如果这没有发生,那么then分支的执行开始,我们保证不会达到我们需要的点5,因为关键点4导致self.array == null。为了不进入关键点3 if语句,您必须执行以下操作之一条件:

  • 为一个值其他 ;
  • 因此other的实际类型不实现IImmutableArray接口

重点5如果使用self.array == null值达到这一点,则说明我们已经实现了目标,该方法中将抛出NullReferenceException类型的异常

我们获得以下数据集,这些数据集将使我们达到目标。

首先:this.array为null

第二个是以下之一:

  • 其他 - ;
  • otherArray类型或它的派生类型
  • other没有Array类型或其派生类,也没有实现IImmutableArray接口

数组是声明如下的字段:

 internal T[] array; 

由于ImmutableArray <T>是结构,因此它具有默认的构造函数(不带参数),其结果是数组字段将成为默认值,即null这就是我们所需要的。

好吧,我们不会忘记我们已经研究了接口方法的显式实现,因此,在调用之前,我们需要执行强制转换。

现在,我们已经准备就绪,可以通过三种方式实现异常。我们连接该库的调试版本,编写代码,执行并观察。

代码段1

 var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(null, comparer); 

代码段2

 var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(new string[] { }, comparer); 

代码段3

 var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(typeof(Object), comparer); 

所有三个代码片段的执行结果将是相同的,只是由于不同的输入数据和不同的执行路径才能实现。

图片18

第54期

如果您没有忘记我们提供的另一种方法,您应该尝试妥协。:)只有这次,我们才不会对它进行详细的分解。而且,我们已经从上一个示例中了解了一些信息。

 int IStructuralComparable.CompareTo(object other, IComparer comparer) { var self = this; Array otherArray = other as Array; if (otherArray == null) { var theirs = other as IImmutableArray; if (theirs != null) { otherArray = theirs.Array; if (self.array == null && otherArray == null) { return 0; } else if (self.array == null ^ otherArray == null) { throw new ArgumentException( SR.ArrayInitializedStateNotEqual, nameof(other)); } } } if (otherArray != null) { IStructuralComparable ours = self.array; return ours.CompareTo(otherArray, comparer); // <= } throw new ArgumentException(SR.ArrayLengthsNotEqual, nameof(other)); } 

PVS-Studio警告V3125在针对空值验证了“我们的”对象之后,使用了该对象。检查行:1265,1251。ImmutableArray_1.cs 1265

如您所见,情况与之前考虑的示例非常相似。

我们将编写以下代码:

 Object other = ....; var comparer = Comparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralComparable)immutableArray).CompareTo(other, comparer); 

让我们尝试选择输入值,以到达发生NullReferenceException类型的异常的地步

other - new String [] {} ;

结果:

图片22

因此,我们再次设法选择了一组输入数据,该方法中发生了输入异常。

第55期 System.Net.HttpListener

项目中有多个位置,不仅可疑,而且彼此非常相似。再一次,关于复制粘贴蔓延的模糊的怀疑。由于模式相同,请考虑一个示例代码;对于其他情况,我将给出分析器警告:

 public override IAsyncResult BeginRead(byte[] buffer, ....) { if (NetEventSource.IsEnabled) { NetEventSource.Enter(this); NetEventSource.Info(this, "buffer.Length:" + buffer.Length + " size:" + size + " offset:" + offset); } if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } .... } 

PVS-Studio警告V3095在对null进行验证之前,已使用“缓冲区”对象。检查行:51,53。HttpRequestStream.cs 51

抛出带有缓冲区== null的ArgumentNullException类型的异常,显式暗示null是此变量的无效值。但是,如果NetEventSource.IsEnabled表达式的值为truebuffernull,则在评估表达式buffer.Length抛出NullReferenceException类型的异常。因此,在检查缓冲区== null之前在这种情况下,问题甚至无法解决。

在其他方法上使用完全相同的模式发出的PVS-Studio警告:

  • V3095在对null进行验证之前,已使用'buffer'对象。检查行:49,51。HttpResponseStream.cs 49
  • V3095在对null进行验证之前,已使用'buffer'对象。检查行:74,75。HttpResponseStream.cs 74

问题56

System.Transactions.Local项目中找到类似的代码

 internal override void EnterState(InternalTransaction tx) { if (tx._outcomeSource._isoLevel == IsolationLevel.Snapshot) { throw TransactionException.CreateInvalidOperationException( TraceSourceType.TraceSourceLtm, SR.CannotPromoteSnapshot, null, tx == null ? Guid.Empty : tx.DistributedTxId); } .... } 

PVS-Studio警告V3095在对null进行验证之前,已使用'tx'对象。检查行:3282,3285。TransactionState.cs 3282

在特定条件下,他们想要引发InvalidOperationException类型的异常。调用该方法时,将使用tx参数创建异常对象,并检查该对象是否为null,以便在评估表达式tx.DistributedTxId不会引发NullReferenceException类型的异常。具有讽刺意味的是,如果tx - null,则此检查将无济于事,因为在计算if语句的条件时访问通过一个实例变量的字段TX - tx._outcomeSource._isoLevelSystem.Runtime.Caching项目中的

Issue 57

代码

 internal void SetLimit(int cacheMemoryLimitMegabytes) { long cacheMemoryLimit = cacheMemoryLimitMegabytes; cacheMemoryLimit = cacheMemoryLimit << MEGABYTE_SHIFT; _memoryLimit = 0; // never override what the user specifies as the limit; // only call AutoPrivateBytesLimit when the user does not specify one. if (cacheMemoryLimit == 0 && _memoryLimit == 0) { // Zero means we impose a limit _memoryLimit = EffectiveProcessMemoryLimit; } else if (cacheMemoryLimit != 0 && _memoryLimit != 0) { // Take the min of "cache memory limit" and // the host's "process memory limit". _memoryLimit = Math.Min(_memoryLimit, cacheMemoryLimit); } else if (cacheMemoryLimit != 0) { // _memoryLimit is 0, but "cache memory limit" // is non-zero, so use it as the limit _memoryLimit = cacheMemoryLimit; } .... } 

PVS-Studio 警告V3022表达式'cacheMemoryLimit!= 0 && _memoryLimit!= 0'始终为false。 CacheMemoryMonitor.cs 250

如果仔细查看代码,您会发现表达式之一-cacheMemoryLimit!= 0 && _memoryLimit!= 0-始终为false。由于_memoryLimit的值为0(放置if语句之前,因此&&运算符的右操作数为false,因此,整个表达式的结果也为false

发行第58期

以下是System.Diagnostics.TraceSource项目中的可疑代码

 public override object Pop() { StackNode n = _stack.Value; if (n == null) { base.Pop(); } _stack.Value = n.Prev; return n.Value; } 

PVS-Studio警告V3125在对null进行验证之后,使用了'n'对象。检查行:115,111。CorrelationManager.cs 115

有趣的代码。由于检查n == null,我将假定null是此局部变量的可能值。在这种情况下,访问实例属性-n.Prev引发NullReferenceException类型的异常。如果n在这种情况下永远不能为null,则在此上下文中调用base.Pop()将永远不会执行。第59期在项目System.Drawing.Primitives中找到一个有趣的地方



再一次,我建议自己寻找问题。这是代码:

 public static string ToHtml(Color c) { string colorString = string.Empty; if (c.IsEmpty) return colorString; if (ColorUtil.IsSystemColor(c)) { switch (c.ToKnownColor()) { case KnownColor.ActiveBorder: colorString = "activeborder"; break; case KnownColor.GradientActiveCaption: case KnownColor.ActiveCaption: colorString = "activecaption"; break; case KnownColor.AppWorkspace: colorString = "appworkspace"; break; case KnownColor.Desktop: colorString = "background"; break; case KnownColor.Control: colorString = "buttonface"; break; case KnownColor.ControlLight: colorString = "buttonface"; break; case KnownColor.ControlDark: colorString = "buttonshadow"; break; case KnownColor.ControlText: colorString = "buttontext"; break; case KnownColor.ActiveCaptionText: colorString = "captiontext"; break; case KnownColor.GrayText: colorString = "graytext"; break; case KnownColor.HotTrack: case KnownColor.Highlight: colorString = "highlight"; break; case KnownColor.MenuHighlight: case KnownColor.HighlightText: colorString = "highlighttext"; break; case KnownColor.InactiveBorder: colorString = "inactiveborder"; break; case KnownColor.GradientInactiveCaption: case KnownColor.InactiveCaption: colorString = "inactivecaption"; break; case KnownColor.InactiveCaptionText: colorString = "inactivecaptiontext"; break; case KnownColor.Info: colorString = "infobackground"; break; case KnownColor.InfoText: colorString = "infotext"; break; case KnownColor.MenuBar: case KnownColor.Menu: colorString = "menu"; break; case KnownColor.MenuText: colorString = "menutext"; break; case KnownColor.ScrollBar: colorString = "scrollbar"; break; case KnownColor.ControlDarkDark: colorString = "threeddarkshadow"; break; case KnownColor.ControlLightLight: colorString = "buttonhighlight"; break; case KnownColor.Window: colorString = "window"; break; case KnownColor.WindowFrame: colorString = "windowframe"; break; case KnownColor.WindowText: colorString = "windowtext"; break; } } else if (c.IsNamedColor) { if (c == Color.LightGray) { // special case due to mismatch between Html and enum spelling colorString = "LightGrey"; } else { colorString = c.Name; } } else { colorString = "#" + cRToString("X2", null) + cGToString("X2", null) + cBToString("X2", null); } return colorString; } 

好吧,好吧,这些都是我的笑话...还是可以吗?无论如何,我们都会缩短代码以更清楚地指出问题所在。

这是代码的简化版:

 switch (c.ToKnownColor()) { .... case KnownColor.Control: colorString = "buttonface"; break; case KnownColor.ControlLight: colorString = "buttonface"; break; .... } 

PVS-Studio警告V3139两个或更多分支机构执行相同的操作。ColorTranslator.cs 302

我不能肯定地说,但是我认为这仍然是一个错误。在其他情况下,当他们想为多个枚举元素返回相同的值时,他们会使用多个随后的情况在我看来,复制粘贴出错很容易。

挖得更深一些。要在分析的ToHtml方法的输出处获取“ buttonface”,可以将以下值之一传递给输入(预期):

  • SystemColors.Control ;
  • SystemColors.ControlLight

如果查看每种颜色的ARGB值,则可以看到以下内容:

  • SystemColors.Control - (255,240,240,240) ;
  • SystemColors.ControlLight-(255,227,227,227)

如果您调用接收到的值(“ buttonface”),则采用相反的转换方法-FromHtml,我们将获得颜色Control(255、240、240、240 我可以从FromHtml获取ControlLight颜色吗?是的在此方法中,有一个颜色表,根据该颜色表(在特定情况下)获得颜色。表初始化程序具有以下行:

 s_htmlSysColorTable["threedhighlight"] = ColorUtil.FromKnownColor(KnownColor.ControlLight); 

因此,FromHtml返回“ threedhighlight”的颜色ControlLight(255、227、227、227)。我认为在KnownColor.ControlLight的情况下应该使用此值

第60期

让我们看一下System.Text.RegularExpressions项目中的一些有趣的警告

 internal virtual string TextposDescription() { var sb = new StringBuilder(); int remaining; sb.Append(runtextpos); if (sb.Length < 8) sb.Append(' ', 8 - sb.Length); if (runtextpos > runtextbeg) sb.Append(RegexCharClass.CharDescription(runtext[runtextpos - 1])); else sb.Append('^'); sb.Append('>'); remaining = runtextend - runtextpos; for (int i = runtextpos; i < runtextend; i++) { sb.Append(RegexCharClass.CharDescription(runtext[i])); } if (sb.Length >= 64) { sb.Length = 61; sb.Append("..."); } else { sb.Append('$'); } return sb.ToString(); } 

PVS-Studio警告V3137分配了“剩余”变量,但在功能结束时未使用该变量。 RegexRunner.cs 612

将值写入剩余局部变量,但是直到方法结束之前不再使用它。也许删除了一些使用它的代码,但他们忘记了变量本身。或者这是一个更严重的错误,应该以某种方式使用此变量。

第61期

 public void AddRange(char first, char last) { _rangelist.Add(new SingleRange(first, last)); if (_canonical && _rangelist.Count > 0 && first <= _rangelist[_rangelist.Count - 1].Last) { _canonical = false; } } 

PVS-Studio 警告V3063如果条件表达式的一部分被评估

,则该部分始终为true:_rangelist.Count>0。RegexCharClass.cs 523 分析器正确地指出,如果该条件表达式的一部分-_rangelist.Count> 0-如果该部分始终为true此代码将被执行。即使_rangelist指向的列表为空,在添加元素_rangelist.Add(....)之后,也不会再这样了。

发行第62期

让我们看一下System.Drawing.CommonSystem.Transactions.Local项目中V3128诊断规则的触发

 private class ArrayEnumerator : IEnumerator { private object[] _array; private object _item; private int _index; private int _startIndex; private int _endIndex; public ArrayEnumerator(object[] array, int startIndex, int count) { _array = array; _startIndex = startIndex; _endIndex = _index + count; _index = _startIndex; } .... } 

PVS-Studio警告V3128在构造函数中初始化“ _index”字段之前,请先使用该字段。 1679个PrinterSettings.Windows.cs

当初始化场_endIndex使用其它字段- _index,其在使用时具有标准值- 默认(INT)0_index字段下方已被初始化。如果突然这不是错误,则_index变量应在此表达式中省略,以免造成混淆。

第63期

 internal class TransactionTable { .... private int _timerInterval; .... internal TransactionTable() { // Create a timer that is initially disabled by specifing // an Infinite time to the first interval _timer = new Timer(new TimerCallback(ThreadTimer), null, Timeout.Infinite, _timerInterval); .... // Store the timer interval _timerInterval = 1 << TransactionTable.timerInternalExponent; .... } } 

PVS-Studio警告V3128在构造函数中初始化“ _timerInterval”字段之前,请先使用该字段。TransactionTable.cs 151

情况与上述类似。首先,使用_timerInterval字段的值(尽管它仍然等于默认值(int))来初始化_timer,然后才初始化_timerInterval字段本身

第64版

,仍然在开发中的诊断规则收到以下警告。他还没有文档和最终报告,但在他的帮助下,他已经设法找到了几个有趣的地方。同样,这些地方看起来像复制粘贴 ,因此我们将只考虑一段代码。

 private bool ProcessNotifyConnection(....) { .... WeakReference reference = (WeakReference)( LdapConnection.s_handleTable[referralFromConnection]); if ( reference != null && reference.IsAlive && null != ((LdapConnection)reference.Target)._ldapHandle) { .... } .... } 

PVS-Studio警告(桩):VXXXX TODO_MESSAGE。 LdapSessionOptions.cs 974

此处的危险是,如果在检查了reference.IsAlive执行了垃圾回收,并且WeakReference引用的对象位于其下,则reference.Target将返回null。结果,当访问实例字段_ldapHandle时,抛出NullReferenceException类型的异常。实际上,Microsoft本身也会通过验证IsAlive警告此陷阱。引用docs.microsoft.com-“ WeakReference.IsAlive属性 ”:Because an object could potentially be reclaimed for garbage collection immediately after the IsAlive property returns true, using this property is not recommended unless you are testing only for a false return value.


分析是否找到了所有这些错误和有趣的地方?当然不是!开始查看分析结果后,我仔细查看了警告。随着他们数量的增加,并且很明显文章上会出现警告,我翻阅了结果,试图只选择对我来说最有趣的东西。当我到达最后一个日志(最大的日志)时,剩下的就是浏览所有警告,直到我的眼睛被异常的东西所吸引。因此,如果您深入研究它,我相信您可以找到更多有趣的地方。

例如,我忽略了几乎所有警告V3022V3063。相对而言,如果有以下形式的代码:

 String str = null; if (str == null) .... 

我想念它,因为我想描述一些有趣的地方。使用不安全锁的触发器,该锁使用带有对此this的锁lock语句,等等。 - V3090 ;不安全事件调用-V3083;这些对象的类型实现了IDisposable,但对于没有呼叫处置 / 关闭 - V3072和类似的诊断;还有更多。

我没有写出来(至少我尝试过,我偶然写了一些东西),但我发现测试中发现了问题。除了几个对我来说似乎很有趣的地方,它引起了我的注意。但是测试代码也可能包含错误,这就是为什么测试无法正常工作的原因。

总的来说,还有很多东西要学习,但是我没有设定一个目标,那就是绝对写出所有发现的问题

代码的质量似乎参差不齐。有些项目非常干净,而另一些则包含可疑的地方。也许可以预期项目的纯度,特别是在最常用的库类的情况下。

如果概括一下,考虑到已经分析了大量的代码,我们可以说代码的质量很高。但是,如下文所述,存在一些阴暗的角落。

顺便说一句,这个规模的项目对分析仪也是一个很好的测试。我设法找到了一系列我选择检查和纠正的错误/奇怪的警告。因此,作为分析的结果,我们设法找到了一些值得在PVS-Studio本身上工作的地方。

结论


如果您在阅读全文后到了这个地方,请让我握手!希望我能够向您展示有趣的错误,并演示静态分析的好处。如果您还为自己学习了一些新知识,可以使您编写更好的代码-我将倍加高兴。

图片24

无论如何,静态分析的帮助不会是多余的,因此我建议您在项目上尝试使用PVS-Studio,并查看可以从中找到哪些有趣的地方。如果您有任何疑问,或者只是想分享发现的有趣地点,请随时发送电子邮件至support@viva64.com :)

祝一切顺利!

PS与.NET Core库开发人员联系


非常感谢您所做的!你是好人 我希望本文有助于使代码更好。请记住,我并没有写下文章中所有可疑的地方,最好是使用分析器独立检查项目-这样,您可以更详细地研究所有警告,比起简单的文本日志/错误列表,它会更方便工作(我为此写了更多内容在这里)。



如果您想与讲英语的读者分享这篇文章,请使用以下链接:Sergey Vasiliev。通过PVS-Studio静态分析器检查.NET Core库源代码

Source: https://habr.com/ru/post/zh-CN463537/


All Articles