通过PVS-Studio静态分析器检查.NET Core库源代码

图片19

.NET Core库是GitHub上最受欢迎的C#项目之一。 由于它已广为人知和使用,因此这不足为奇。 因此,试图揭示源代码的黑暗角落变得更加迷人。 这就是我们将尝试在PVS-Studio静态分析仪的帮助下进行的操作。 您怎么看-我们最终会发现一些有趣的东西吗?

一年半来,我一直在朝着这篇文章迈进。 在某个时候,我脑子里浮现出一个想法,.NET Core库是一个花哨的东西,它的检查很有希望。 我多次检查了该项目,分析器不断发现越来越多的有趣代码片段,但是它并不仅仅是滚动警告列表。 这就是-终于发生了! 项目已选中,文章就在您眼前。

有关项目和检查的详细信息


如果您正在努力进行代码调查-您可以省略此部分。 但是,我非常希望您阅读它,因为在这里我将向您介绍有关项目和分析器的更多信息,以及进行分析和重现错误的更多信息。

检查项目


也许,我可以跳过讲什么是CoreFX(.NET核心库),但是如果您还没有听说,请在下面进行描述。 与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下有效,而C,C ++,Java代码则可以在Windows,Linux和macOS下进行分析。

通常对于检查C#项目,我使用Visual Studio的PVS-Studio插件(支持2010-2019版本),因为在这种情况下,它可能是最简单便捷的分析方案:打开解决方案,运行分析,处理警告列表。 但是,使用CoreFX变得更加复杂。

棘手的部分是该项目没有单个.sln文件,因此无法在Visual Studio中打开该文件并使用PVS-Studio插件执行完整的分析。 这可能是一件好事-我真的不知道Visual Studio如何应对这种规模的解决方案。

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

快点! 结果,我有一个Pandora盒子,里面有一组存储一些有趣内容的报告。 如果需要,可以将这些日志与PlogConverter实用程序合并,作为分发的一部分。 对我来说,使用单独的日志更加方便,因此我没有合并它们。

在描述一些错误时,请参阅docs.microsoft.com和NuGet软件包中的文档,这些文档可从nuget.org下载。 我认为文档/软件包中描述的代码可能与所分析的代码略有不同。 但是,例如,如果文档没有描述具有特定输入数据集时生成的异常,但是新的软件包版本将包含这些异常,这将非常奇怪。 您必须承认这将是一个可疑的惊喜。 使用与调试库相同的输入数据,从NuGet中重现包中的错误,这表明此问题不是新问题。 最重要的是,您可以“触摸”它,而无需从源代码构建项目。

因此,考虑到代码在理论上可能不同步的可能性,我发现可以参考docs.microsoft.com上相关方法的描述并使用nuget.org中的程序包重现问题是可以接受的。

另外,我想指出的是,在撰写本文的过程中,给定链接的描述,包中的信息(注释)(在其他版本中)可能已更改。

其他检查项目


顺便说一下,本文并不是同类文章中唯一的。 我们在项目检查方面写其他文章。 通过此链接,可以找到已检查项目列表 。 此外,在我们的网站上,您不仅可以找到项目检查的文章,而且还可以找到有关C,C ++,C#,Java的各种技术文章,以及一些有趣的注释。 您可以在博客中找到所有这些内容。

我的同事以前已经在2015年检查过.NET Core库。以前的分析结果可以在相关文章中找到:“ .NET Core库的圣诞节分析(CoreFX)”

检测到的错误,可疑和有趣的片段


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

为了方便起见,我已经使用Issue 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 - context.ContextType的无条件取消引用。 结果,如果上下文值为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条件是true还是false,都将执行相同的操作,因为thenif语句的其他分支具有相同的主体。 分支中应该有另一个动作,或者您可以省略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

同样,我们看到一个奇怪的动作顺序。 在该方法中,有一个check propertyNames!= Null ,即开发人员从null覆盖方法的基础。 但是在上面,您可以通过此可能为空的引用-propertyNames.LengthpropertyNames [i]看到一些访问操作。 结果是非常可预测的-如果将null引用传递给方法,则会发生NullReferenceExcepption类型的异常。

真是巧合! RefreshCache是公共类中的公共方法。 尝试重现该怎么办? 为此,我们将需要的库System。Directory Services包含到项目中,并编写如下代码:

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

执行代码后,我们可以看到期望的结果。

图片3

只是为了踢球,您可以尝试在NuGet软件包的发行版上重现该问题。 接下来,我们将对System.DirectoryServices包的引用(我使用的是4.5.0版)添加到项目中,并执行已经熟悉的代码。 结果如下。

图片4

第4期

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

我们将使用以下代码:

 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

它是上述代码使用版本4.5.1的NuGet System.Drawing.Common包的输出。 下一步是使用调试库版本运行相同的代码。 这是我们看到的:

图片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(Stream)方法 ”。 但是,对于输入或有关生成的异常的信息也没有限制。

现在让我们继续进行代码检查。

 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期

我们继续进行审查并继续。 尝试找出在情况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

第二个条件没有问题,关于第一个条件不能说。 由于名称实例字段是在下面无条件访问的,并且如果为sym - null ,则将发生NullReferenceException类型的异常。

你瞎了吗 在Debug.Assert调用中,检查了sym!= Null ”-有人可能会争论。 恰恰相反,这就是重点! 在发布版本中工作时, Debug.Assert不会有任何帮助,并且在上述条件下,我们将获得的只是NullReferenceException 。 而且,我已经在Microsoft Roslyn的另一个项目中看到了类似的错误,在Debug.Assert中也发生了类似的情况。 让我为罗斯林转一会儿。

使用Microsoft.CodeAnalysis库时可能会重现此问题,或者使用语法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-Studio警告: V3088该表达式用括号括起来两次:((表达式))。 不需要一对括号,否则会出现打印错误。 孤立的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 ,并且_streamSet将为true。

第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方法 ”。 似乎这里没有什么特别的。

好吧,让我们执行代码,aaand ...

图片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

在这里,我们可以看到2个片段,其中当前的ToString实现可以返回null。 至此,我们将回顾Microsoft关于ToString方法实现的建议。 因此,请参考docs.microsoft.com-“ Object.ToString 方法 ”:

对继承者的说明.... ToString()方法的重写应遵循以下准则:

  • ....
  • 您的ToString()重写不应返回Empty或 字符串。
  • ....

这就是PVS-Studio警告的地方。 上面给出的用于重现该问题的两个代码片段分别具有不同的退出点-分别是第一个和第二个返回点。 让我们深入一点。

第一种情况。 CountStringCollection基类的属性。 由于未添加任何元素, Count == 0 ,条件Count <= 0为true,则返回值。

在第二种情况下,我们使用实例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的of为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库中的有趣内容以及同名程序包代码。

 public struct RepeatBehavior : IFormattable { .... public override string ToString() { return InternalToString(null, null); } .... } 

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

我们已经知道的熟悉的故事-ToString方法可以返回null值。 因此,调用者代码的作者可能会不愉快地感到惊讶,该调用者的代码假定RepeatBehavior.ToString始终返回非null引用。 同样,它与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; } } 

分析器检测到,如果默认分支在switch中执行, InternalToString将返回值。 因此, ToString也将返回null

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分支中的任何值,并且默认分支已接收控件。 那就是我们想要做的。

我也想指出,该方法的注释和docs.microsoft.com均未指定该方法可以返回值。

第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在'|'的左侧和右侧有相同的子表达式' CharType.Comment '。 操作员。 XmlUTF8TextReader.cs 56
  • V3001在'|'的左侧和右侧有相同的子表达式' CharType.Comment '。 操作员。 XmlUTF8TextReader.cs 58
  • V3001在'|'的左侧和右侧有相同的子表达式' CharType.Comment '。 操作员。 XmlUTF8TextReader.cs 64

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

第19期

让我们继续。 让我们在方法说明和docs.microsoft.com-“ XmlBinaryWriterSession.TryAdd(XmlDictionaryString,Int32)方法 ”中检查有关XmlBinaryWriterSession.TryAdd方法的返回值的信息: 返回:如果可以添加字符串,则返回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

好吧,我们已经走了很长一段路! 因此,在继续之前,我建议您休息一会儿:振奋肌肉,四处走走,休息一下眼睛,看着窗外...

图片24

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

第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

分析器警告评估哈希器时, 哈希器变量的值可以为null TransformBlock表达式会导致NullReferenceException类型的异常。 由于过程间分析,该警告的发生成为可能。

因此,要了解在这种情况下hasher是否可以采用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、6。该的cbReturn值必须> 0(但是,在成功创建数组的适当范围内)。的合规性cbReturn> 0条件是需要的条件的进一步的满足IB <rgbT.Length和环回输入到旧体。

7Helpres.ConvertIntToByteArray必须正常工作。

为了满足依赖于方法参数的条件,仅传递适当的参数就足够了,例如:

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

为了“ 取消信用CryptoConfig.CreateFromName方法,我们需要能够更改_hashNameValue字段的值幸运的是,我们有了它,因为该类为此字段定义了包装器属性:

 public string HashName { get { return _hashNameValue; } set { _hashNameValue = value ?? DefaultHash; } } 

通过为HashName设置一个“合成”值(即_hashNameValue),我们可以在标记的第一个出口点CreateFromName方法中获取由于该方法很大,因此我不会详细分析该方法(希望您对此宽恕)。结果,导致NullReferenceException类型异常的代码可能如下所示:



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

现在,我们添加对调试库的引用,运行代码并获得预期的结果:

图片10

只是为了好玩,我尝试使用4.3.1版本的NuGet包执行相同的代码。

图片14

方法描述中没有有关生成的异常,输出参数限制的信息。Docs.microsoft.com PKCS1MaskGenerationMethod.GenerateMask(Byte [],Int32)方法 “也未指定。

顺便说一句,在撰写本文并描述重现问题的操作顺序时,我发现了另外两种方法“中断”此方法:

  • 传递太大的值作为cbReturn参数;
  • 值作为rgbSeed传递。

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

图片15

在第二种情况下,执行rgbSeed.Length表达式,我们将获得NullReferenceException类型的异常在这种情况下,重要的是,哈希值必须为非null。否则,控制流将不会到达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方法中,它们将返回。此外,在调用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

用于循环被执行时,表达我<seq.Count被用作与所述一个的退出条件。它提出了开发人员要绕过seq序列的想法。但是在循环中,作者访问序列元素不是通过使用counter -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类型的异常。在变量nodeSeq下方进行检查。nodeSeq是通过显式seq强制转换获得的,但尚不清楚为什么进行检查。如果seq值为null,则由于异常,控制流将无法进行此检查。如果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 Method中的提示之一





您的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

的下面的代码首先设置一个空:字符串为_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在验证是否为空之前使用了“其他”对象。检查行:1095,1096。XPathNodePointer.cs 1095

表达式other!= Null作为Debug.Assert方法的参数,这表明ComparePosition方法可以获取null值作为参数。至少,目的是要抓住这种情况。但是同时,other.RealFoliate实例方法上方的行被调用。结果,如果other具有null值,则在通过Assert检查之前,将生成NullReferenceException类型的异常

第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是一个实例方法。根据check attr == nullnull是此变量的可接受(预期)值。因此,如果控制流到达||的正确操作数运算符(如果attr - null),我们将获得NullReferenceException类型的异常因此,异常发生的条件如下:



  1. The value of attrnull . The right operand of the && operator is evaluated.
  2. The value of !attribute.IsDefaultAttribute()false . The overall result of the expression with the && operator — false .
  3. Since the left operand of the || operator is of the false value, the right operand is evaluated.
  4. Since attrnull , when calling the Match method, an exception is generated.

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语句包含相等的expression- table == null。与,然后这些语句的分支包含不同的动作-在第一种情况下,与该值的方法退出-1,在第二个-生成异常。的该表变量没有改变用于检查之间。因此,不会生成考虑的异常。

第34期

System.ComponentModel看有趣的方法。类型转换器项目。好吧,让我们先阅读注释,并对其进行描述:

从格式化的字符串中删除最后一个字符。 (删除虚拟字符串中的最后一个字符)。退出时,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 .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-Studio警告: V3066传递给“ 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期

这对我来说似乎是不寻常的代码。模式在所有三个检测到的情况下看起来都是相同的,并且位于名称和变量名称相同的方法中。因此:

  • 我或者没有足够的悟性来达到这种重复的目的;
  • 或者错误是通过复制粘贴方法传播的。

代码本身:

 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

分析器假定给定表达式始终具有真实值。真的是这样。甚至quotePrefix中的实际值是多少也没关系 -条件本身写得不正确。让我们深入到此。

我们有||操作者,所以表达式值将是,如果左侧或右侧(或两者)的操作数将具有值。左边的一句话很清楚。仅当左边的一个为false时,才会评估右边的一个价值。意味着这一点,如果表达式在那的右操作数的值总是方式组成为true的时候的一个左的值是,则整个表达式的结果永远会的是

下面的代码上面的从结果我们那知道如果有合适的操作数是评估的价值表达string.IsNullOrEmpty(quotePrefix) - 为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期

有一个可疑的代码,我们已经遇到了2次。模式是相同的。

 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)行[“ DataType”]看起来可疑。首先,将执行显式转换,之后-通过as运算符进行转换。如果值行[“ DataType”] - null,则它将成功地“传递”两个转换,并将其作为Add方法的参数。如果行[“ 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项目并浏览几个警告。针对以下代码发出了两个警告:

 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); 

了equals方法不评论,我并没有在找到.NET核心的这种方法描述AT docs.microsoft.com,只为.NET框架。如果我们查看它(“ LazyMemberInfo.Equals(Object)方法 ”)-无论它返回true还是false我们都不会看到任何特别的东西,因为它没有关于生成的异常的信息。我们将执行代码并查看:

图片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是一个结构,因此将其取消装箱。的值拆箱功能,在转弯,导致发生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

第四十八期

另一个有趣的片段:

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

PVS-Studio警告: V3042可能为NullReferenceException。 '?。'和“。” 179操作员用于访问“属性”对象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

分析器被检查弄糊涂了!=在其中一种情况下为空。好像它在重构期间丢失了,因为被多次取消引用。如果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-Studio警告: V3053表达式过多。检查子字符串“名称”和“ db_name”。 QueryEngine.cs 151

事实是,在这种情况下,子表达式lowerBatchText.Contains(“ name”)lowerBatchText.Contains(“ db_name”)的组合是多余的。确实,如果检查的字符串包含子字符串“ db_name”,那么它也将包含“ name”子字符串。如果字符串不包含“ name”,那么它也将不包含“ db_name”。结果,结果是检查lowerBatchText.Contains(“名称”)是多余的。如果所检查的字符串不包含“ 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验证为空后使用了“入口”对象。检查行:270,227。FtpControlStream.cs 270

在编写插值字符串时,使用诸如entry ?、命令响应之类的表达式。 '?。'使用运算符代替“。” 如果任何相应的参数具有,则运算符不会获取NullReferenceException类型的异常。在这种情况下,此技术有效。此外,正如我们从代码中看到的那样,可能响应null被拆分(如果response == null,则从方法中退出)),而没有类似的条目。甲作为时,如果结果的条目 - 此外沿着下面的代码评估时entry.Command由BE的异常生成意志,一个(带的,不使用“”?')。

此时,相当详细的代码审查正在等待我们,因此我建议您再休息一下-放松一下,喝点茶或咖啡。之后,我将在这里继续。

图片21

你回来了吗 那我们继续吧。 :)

第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在将其验证为null后使用了“我们的”对象。检查行:1212,1204。ImmutableArray_1.cs 1212 通过最后一个返回表达式中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相同我们可以为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); } 

关键点1. self.array == this.array(由于self = this)。因此,在调用该方法之前,我们需要获取条件this.array == null

重点2。我们可以忽略if,这将是获得我们想要的最简单的方法。若要忽略if,我们只需要另一个变量为Array类型或派生变量,而不包含值。这样,在使用as运算符之后,将在otherArray中写入非null引用,而我们将忽略第一个if语句

重点3这一点需要更复杂的方法。我们绝对需要在第二条if语句(具有条件表达式“ 他们的!”的那条语句)中退出如果没有发生,然后分支开始执行,则最肯定的是由于关键点4 ,在条件self.array == null的情况下,我们不会获得所需的点5。为避免输入关键点的if语句3,必须满足以下条件之一:

  • 其他的值与所述BE ;
  • 实际的其他类型一定不能实现IImmutableArray接口。

重点5如果使用self.array == null值达到这一点,则意味着我们已经达到了目标,并且将生成NullReferenceException类型的异常

我们获得以下数据集,这些数据集将引导我们到达所需的位置。

首先:this.array-null

第二-以下之一:

  • 其他 - ;
  • 另一个具有Array类型或从其派生的类型;
  • other没有Array类型或它的派生类,因此没有实现IImmutableArray接口。

array是字段,通过以下方式声明:

 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在将其验证为null后使用了“我们的”对象。检查行: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。HTTP侦听器项目中,我偶然发现了几个可疑和非常相似的地方。再一次,我对在这里进行的复制粘贴感到震惊。由于模式相同,因此我们将看一个代码示例。对于其余情况,我将引用分析器警告。

 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表达式的值为truebuffer - null,则在计算buffer.Length表达式时,将生成NullReferenceException类型的异常。如我们所见,我们甚至都无法达到在这种情况下,缓冲区==空检查。

针对使用以下模式的其他方法发出的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在验证是否为空之前使用了'tx'对象。检查行:3282,3285。TransactionState.cs 3282

在特定条件下,作者希望引发InvalidOperationException类型的异常。在调用用于创建异常对象的方法时,代码作者使用tx参数,将其检查为null以避免在评估tx.DistributedTxId表达式出现NullReferenceException类型的异常。具有讽刺意味的是,检查将无济于事,因为在评估if语句的条件时,实例字段是通过tx变量访问的-tx._outcomeSource._isoLevelSystem.Runtime.Caching项目中的

第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如果条件表达式的一部分被评估为:_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”字段之前,必须先使用该字段。 PrinterSettings.Windows.cs 1679

初始化_endIndex字段时,将使用另一个_index字段,该字段在使用时具有标准值default(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问题

诊断规则发出了下一个警告,该规则仍在开发中。没有文档或最终信息,但是在它的帮助下我们已经找到了几个有趣的片段。同样,这些片段看起来像copy-paste,因此我们将仅考虑一个代码片段。

 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指向的对象将被垃圾收集。在这种情况下,Target将返回值。结果,当访问实例字段_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.

Summary on Analysis


在分析过程中是否找到了所有这些错误和有趣的地方?当然不是!查看分析结果时,我正在彻底检查警告。随着数量的增加,而且很显然有足够的篇幅来撰写一篇文章,我正在浏览结果,试图仅选择对我而言最有趣的那些。当我到达最后一个(最大的原木)时,我只能查看警告,直到发现异常情况为止。因此,如果您四处挖掘,我相信您会找到更多有趣的地方。

例如,我几乎忽略了所有V3022V3063警告。可以这么说,如果我遇到过这样的代码:

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

我会忽略它,因为我想描述许多其他有趣的地方。被警告,对有不安全使用的锁lock语句与锁定的这等- V3090 ;不安全事件调用-V3083 对象时,其类型都实现了IDisposable,但对于其中的Dispose / 关闭不叫- V3072和广告商选择相似的诊断等等。

我也没有注意到用测试编写的问题。至少,我尝试过,但可能会不小心服用一些。除了几个我发现足以引起人们注意的有趣地方。但是测试代码也可能包含错误,由于这些错误,测试将无法正常工作。

通常,仍有许多事情需要调查-但我无意标记所有发现的问题

代码的质量对我来说似乎并不平衡。一些项目非常干净,其他项目包含可疑的地方。也许我们可能希望有一个干净的项目,尤其是涉及最常用的库类时。

综上所述,我们可以说该代码质量很高,因为它的数量很多。但是,正如本文所建议的那样,这里有一些阴暗的角落。

顺便说一句,这个规模的项目对分析仪也是一个很好的测试。我设法找到了一些我选择要研究和纠正的错误/怪异的警告。因此,作为分析的结果,我设法找到了必须在PVS-Studio本身上进行工作的地方。

结论


如果您通过阅读整篇文章到达了这个地方-让我握手!希望我能够向您展示有趣的错误并证明静态分析的好处。如果您为自己学习了一些新知识,那将使您编写更好的代码-我将倍加高兴。

图片23

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

最好的问候!

PS for .NET Core库开发人员


非常感谢您所做的一切!干得好!希望本文能帮助您使代码更好。记住,我还没有写所有可疑的地方,所以您最好自己使用分析仪检查项目。这样,您将能够详细调查所有警告。而且,使用它会比使用简单的文本日志/错误列表(我在这里更详细地介绍过)更方便。

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


All Articles