{"id":387889,"date":"2024-06-29T07:36:07","date_gmt":"2024-06-29T07:36:07","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=387889"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=387889","title":{"rendered":"<span>Playing with null: Checking MonoGame with the PVS-Studio analyzer<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>The PVS-Studio analyzer often checks code of libraries, frameworks, and engines for game development. Today we check another project \u2014 MonoGame, a low-level gamedev framework written in C#.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fab\/aa2\/f4c\/fabaa2f4c077b87a30be4942cf03eba7.png\" width=\"780\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/fab\/aa2\/f4c\/fabaa2f4c077b87a30be4942cf03eba7.png\"\/><figcaption><\/figcaption><\/figure>\n<h3>Introduction<\/h3>\n<p>MonoGame is an open-source framework for game development. It&#8217;s the heir of the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Microsoft_XNA\">XNA<\/a> project, which was developed by Microsoft until 2013.<\/p>\n<p>Let me also remind you about what <a href=\"https:\/\/pvs-studio.com\/en\/pvs-studio\/\">PVS-Studio<\/a> is :). PVS-Studio is a static code analyzer that searches for various code errors and security-related vulnerabilities. I used PVS-Studio version 7.16 and <a href=\"https:\/\/github.com\/MonoGame\/MonoGame\">MonoGame sources<\/a> from 12.01.2022.<\/p>\n<p>It&#8217;s worth mentioning that the analyzer issued a couple of warnings on some libraries used in the project \u2014 DotNetZip and NVorbis. I described them below. If you want, you can easily <a href=\"https:\/\/pvs-studio.com\/en\/docs\/manual\/0014\/\">exclude third-party code<\/a> from your analysis.<\/p>\n<h3>Analyzer warnings<\/h3>\n<p><strong>Issue 1<\/strong><\/p>\n<pre><code class=\"cs\">public void Apply3D(AudioListener listener, AudioEmitter emitter)  {   ....   var i = FindVariable(\"Distance\");   _variables[i].SetValue(distance);   ....   var j = FindVariable(\"OrientationAngle\");   _variables[j].SetValue(angle);   .... } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3106\/\">V3106<\/a> Possible negative index value. The value of &#8216;i&#8217; index could reach -1. MonoGame.Framework.DesktopGL(netstandard2.0) Cue.cs 251<\/p>\n<p>The analyzer noticed that the <em>i<\/em> variable can have value -1. This variable was used as an index.<\/p>\n<p>The <em>i<\/em> variable is initialized by the return value of the <em>FindVariable<\/em> method. Let&#8217;s look inside this method:<\/p>\n<pre><code class=\"cs\">private int FindVariable(string name) {   \/\/ Do a simple linear search... which is fast   \/\/ for as little variables as most cues have.   for (var i = 0; i &lt; _variables.Length; i++)   {     if (_variables[i].Name == name)     return i;   }    return -1; } <\/code><\/pre>\n<p>If no element with the corresponding value in the collection is found, then the return value is -1. Obviously, using a negative number as an index will lead to <em>IndexOutOfRangeException<\/em>.<\/p>\n<p><strong>Issue 2<\/strong><\/p>\n<p>The next problem was also found in the <em>Apply3D<\/em> method:<\/p>\n<pre><code class=\"cs\">public void Apply3D(AudioListener listener, AudioEmitter emitter) {   ....   lock (_engine.UpdateLock)   {     ....     \/\/ Calculate doppler effect.     var relativeVelocity = emitter.Velocity - listener.Velocity;     relativeVelocity *= emitter.DopplerScale;   } } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3137\/\">V3137<\/a> The &#8216;relativeVelocity&#8217; variable is assigned but is not used by the end of the function. MonoGame.Framework.DesktopGL(netstandard2.0) Cue.cs 266<\/p>\n<p>The analyzer warns us that the value was assigned, but never used further.<\/p>\n<p>Someone might get confused by the fact that the code is in the <em>lock<\/em> block, but&#8230; It means nothing for <em>relativeVelocity<\/em> because this variable is declared locally and doesn&#8217;t participate in the inter-thread communication.<\/p>\n<p>Maybe the value of <em>relativeVelocity<\/em> should be assigned to a field.<\/p>\n<p><strong>Issue 3<\/strong><\/p>\n<pre><code class=\"cs\">private void SetData(int offset, int rows, int columns, object data) {   ....   if(....)   {     ....   }   else if (rows == 1 || (rows == 4 &amp;&amp; columns == 4))    {     \/\/ take care of shader compiler optimization     int len = rows * columns * elementSize;     if (_buffer.Length - offset > len)           len = _buffer.Length - offset;    \/\/  &lt;=     Buffer.BlockCopy(data as Array,                      0,                      _buffer,                      offset,                      rows*columns*elementSize);   }   .... } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3137\/\">V3137<\/a> The &#8216;len&#8217; variable is assigned but is not used by the end of the function. MonoGame.Framework.DesktopGL(netstandard2.0) ConstantBuffer.cs 91<\/p>\n<p>Another warning about a value assigned but never used.<\/p>\n<p>The <em>len<\/em> variable is initialized with the following expression:<\/p>\n<pre><code class=\"cs\">int len = rows * columns * elementSize; <\/code><\/pre>\n<p>If you look closely at the code, you might feel deja vu, because this expression repeats one more time:<\/p>\n<pre><code class=\"cs\">Buffer.BlockCopy(data as Array, 0,                  _buffer,                  offset,                  rows*columns*elementSize);    \/\/ &lt;= <\/code><\/pre>\n<p>Most likely, <em>len<\/em> was supposed to be in this place.<\/p>\n<p><strong>Issue 4<\/strong><\/p>\n<pre><code class=\"cs\">protected virtual object EvalSampler_Declaration(....) {   if (this.GetValue(tree, TokenType.Semicolon, 0) == null)     return null;            var sampler = new SamplerStateInfo();   sampler.Name = this.GetValue(tree, TokenType.Identifier, 0) as string;   foreach (ParseNode node in nodes)     node.Eval(tree, sampler);            var shaderInfo = paramlist[0] as ShaderInfo;   shaderInfo.SamplerStates.Add(sampler.Name, sampler);    \/\/ &lt;=            return null; } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3156\/\">V3156<\/a> The first argument of the &#8216;Add&#8217; method is not expected to be null. Potential null value: <a href=\"http:\/\/sampler.Name\">sampler.Name<\/a>. MonoGame.Effect.Compiler ParseTree.cs 1111<\/p>\n<p>The analyzer warns us that the <em>Add<\/em> method is not designed to take <em>null<\/em> as a first argument. At the same time the analyzer warns us that the first argument <a href=\"http:\/\/sampler.Name\"><em>sampler.Name<\/em><\/a>, passed to <em>Add<\/em>, can be <em>null<\/em>.<\/p>\n<p>To begin with, let&#8217;s look at the <em>shaderInfo.SamplerStates<\/em> field:<\/p>\n<pre><code class=\"cs\">public class ShaderInfo {   ....    public Dictionary&lt;string, SamplerStateInfo> SamplerStates =      new Dictionary&lt;string, SamplerStateInfo>(); } <\/code><\/pre>\n<p>It&#8217;s a dictionary and <em>Add<\/em> is a standard method. Indeed, <em>null<\/em> cannot be a dictionary key.<\/p>\n<p>The value of the <a href=\"http:\/\/sampler.Name\"><em>sampler.Name<\/em><\/a> field is passed as the dictionary key. A potential <em>null<\/em> can be assigned in this line:<\/p>\n<pre><code class=\"cs\">sampler.Name = this.GetValue(tree, TokenType.Identifier, 0) as string; <\/code><\/pre>\n<p>The <em>GetValue<\/em> method can return <em>null<\/em> or an instance of any type other than <em>string<\/em>. Thus, the result of casting via the <em>as<\/em> operator is <em>null<\/em>.  Could it be? Let&#8217;s look at <em>getValue<\/em>:<\/p>\n<pre><code class=\"cs\">protected object GetValue(ParseTree tree,                           TokenType type,                           ref int index) {   object o = null;   if (index &lt; 0) return o;    \/\/ left to right   foreach (ParseNode node in nodes)   {     if (node.Token.Type == type)     {       index--;       if (index &lt; 0)       {         o = node.Eval(tree);         break;       }     }   }   return o; } <\/code><\/pre>\n<p>So, this method can return <em>null<\/em> in two cases:<\/p>\n<ol>\n<li>\n<p>If the passed <em>index<\/em> value is less than 0;<\/p>\n<\/li>\n<li>\n<p>If an element of the <em>nodes<\/em> collection that matches the passed <em>type<\/em> was not found.<\/p>\n<\/li>\n<\/ol>\n<p>The developer should have added <em>null<\/em> check for the return value of the <em>as<\/em> operator.<\/p>\n<p><strong>Issue 5<\/strong><\/p>\n<pre><code class=\"cs\">internal void Update() {   if (GetQueuedSampleCount() > 0)   {     BufferReady.Invoke(this, EventArgs.Empty);   } } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3083\/\">V3083<\/a> Unsafe invocation of event &#8216;BufferReady&#8217;, NullReferenceException is possible. Consider assigning event to a local variable before invoking it. MonoGame.Framework.DesktopGL(netstandard2.0) Microphone.OpenAL.cs 142<\/p>\n<p>The analyzer warns about an unsafe invocation of event that potentially has no subscribers.<\/p>\n<p>Before the event invocation, the return value of the <em>GetQueuedSampleCount<\/em> method is checked. If the presence of subscribers to the event does not depend on the truth of the condition, then a <em>NullReferenceException<\/em> may be thrown when this event is called.<\/p>\n<p>If the truth of the expression &#171;<em>GetQueuedSampleCount() > 0><\/em>&#187; guarantees the presence of subscribers, the problem still remains. The state can change between the check and the invocation. The <em>BufferReady<\/em> event is declared like this:<\/p>\n<pre><code class=\"cs\">public event EventHandler&lt;EventArgs> BufferReady; <\/code><\/pre>\n<p>Note that the <em>public<\/em> access modifier allows other developers to use the <em>BufferReady<\/em> event in any code. This increases the chance of performing operations with the event in other threads.<\/p>\n<p>Thus, adding <em>null<\/em> check in the condition does not prevent from <em>NullReferenceException<\/em>, because the <em>BufferReady<\/em> state can change between the check and the invocation.<\/p>\n<p>The easiest way to fix it is to add Elvis operator &#8216;?.&#8217; to the <em>Invoke<\/em> call:<\/p>\n<pre><code class=\"cs\">BufferReady?.Invoke(this, EventArgs.Empty); <\/code><\/pre>\n<p>If this option is not available for some reason, assign <em>BufferReady<\/em> to a local variable and work with it:<\/p>\n<pre><code class=\"cs\">EventHandler&lt;EventArgs> bufferReadyLocal = BufferReady; if (bufferReadyLocal != null)   bufferReadyLocal.Invoke(this, EventArgs.Empty); <\/code><\/pre>\n<p>Errors with <em>public<\/em> events in multi-threaded code may appear rarely, but they are very malicious. These errors are hard or even impossible to reproduce. You can read more about safer work with operators in the <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3083\/\">V3083<\/a> documentation.<\/p>\n<p><strong>Issue 6<\/strong><\/p>\n<pre><code class=\"cs\">public override TOutput Convert&lt;TInput, TOutput>(   TInput input,   string processorName,   OpaqueDataDictionary processorParameters) {   var processor = _manager.CreateProcessor(processorName,                                                  processorParameters);   var processContext = new PipelineProcessorContext(....);   var processedObject = processor.Process(input, processContext);   .... } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3080\/\">V3080<\/a> Possible null dereference. Consider inspecting &#8216;processor&#8217;. MonoGame.Framework.Content.Pipeline PipelineProcessorContext.cs 55<\/p>\n<p>The analyzer warns about possible dereference of the null reference when <em>processor.Process<\/em> is called.<\/p>\n<p>An object of the <em>processor<\/em> class is created via the <em>_manager.CreateProcessor<\/em> call. Let&#8217;s look at its code fragment:<\/p>\n<pre><code class=\"cs\">public IContentProcessor CreateProcessor(                     string name,                     OpaqueDataDictionary processorParameters) {   var processorType = GetProcessorType(name);   if (processorType == null)     return null;   .... } <\/code><\/pre>\n<p>We see that <em>CreateProcessor<\/em> returns <em>null<\/em> if <em>GetProcessorType<\/em> also returns <em>null<\/em>. Well, let&#8217;s look at the method&#8217;s code:<\/p>\n<pre><code class=\"cs\">public Type GetProcessorType(string name) {   if (_processors == null)     ResolveAssemblies();    \/\/ Search for the processor type.   foreach (var info in _processors)   {     if (info.type.Name.Equals(name))       return info.type;   }    return null; } <\/code><\/pre>\n<p>This method can return <em>null<\/em> if no matching element was found in the collection. If <em>GetProcessorType<\/em> returns <em>null<\/em>, then <em>CreateProcessor<\/em> also returns <em>null<\/em>, which will be written to the <em>processor<\/em> variable. As a result, <em>NullReferenceException<\/em> will be thrown if we call the <em>processor.Process<\/em> method.<\/p>\n<p>Let&#8217;s go back to the <em>Convert<\/em> method from the warning. Have you noticed that it has the <em>override<\/em> modifier? This method is an implementation of a contract from an abstract class. Here&#8217;s this abstract method:<\/p>\n<pre><code class=\"cs\">\/\/\/ &lt;summary> \/\/\/ Converts a content item object using the specified content processor. \/\/\/.... \/\/\/ &lt;param name=\"processorName\">Optional processor  \/\/\/ for this content.&lt;\/param> \/\/\/.... public abstract TOutput Convert&lt;TInput,TOutput>(   TInput input,   string processorName,   OpaqueDataDictionary processorParameters ); <\/code><\/pre>\n<p>The comment to the <em>processorName<\/em> input parameter implies that this parameter is optional. Perhaps the developer, seeing such a comment for the signature, will be sure that checks for <em>null<\/em> or empty strings were made in the contract implementations. But this implementation does not have any check.<\/p>\n<p>Detection of potential dereference of a null reference allows us to find a number of possible sources of problem. For example:<\/p>\n<ul>\n<li>\n<p>the correct work requires a non-empty and non-<em>null<\/em> string value, contrary to the comment to the abstract method signature.<\/p>\n<\/li>\n<li>\n<p>a large number of <em>null<\/em>-value returns, which are accessed without check. As a result, this may lead to <em>NullReferenceException<\/em>.<\/p>\n<\/li>\n<\/ul>\n<p><strong>Issue 7<\/strong><\/p>\n<pre><code class=\"cs\">public MGBuildParser(object optionsObject) {   ....   foreach(var pair in _optionalOptions)   {     var fi = GetAttribute&lt;CommandLineParameterAttribute>(pair.Value);     if(!string.IsNullOrEmpty(fi.Flag))       _flags.Add(fi.Flag, fi.Name);   } } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3146\/\">V3146<\/a> Possible null dereference of &#8216;fi&#8217;. The &#8216;FirstOrDefault&#8217; can return default null value. MonoGame.Content.Builder CommandLineParser.cs 125<\/p>\n<p>This warning is also about possible <em>NullReferenceException<\/em>, since the return value of <em>FirstOrDefault<\/em> wasn&#8217;t checked for <em>null<\/em>.<\/p>\n<p>Let&#8217;s find this <em>FirstOrDefault<\/em> call. The <em>fi<\/em> variable is initialized with the value returned by the <em>GetAttribute<\/em> method. The <em>FirstOrDefault<\/em> call from the analyzer&#8217;s warning is there. The search didn&#8217;t take too much time:<\/p>\n<pre><code class=\"cs\">static T GetAttribute&lt;T>(ICustomAttributeProvider provider)                          where T : Attribute {   return provider.GetCustomAttributes(typeof(T),false)                  .OfType&lt;T>()                  .FirstOrDefault(); } <\/code><\/pre>\n<p>A <em>null<\/em> conditional operator should be used to protect code from <em>NullReferenceException<\/em>.<\/p>\n<pre><code class=\"cs\">if(!string.IsNullOrEmpty(fi?.Flag)) <\/code><\/pre>\n<p>Consequently, if <em>fi<\/em> is <em>null<\/em>, then when we try to access the <em>Flag<\/em> property, we&#8217;ll get <em>null<\/em> instead of an exception. The return value of <em>IsNullOrEmpty<\/em> for <em>null<\/em> argument is <em>false<\/em>.<\/p>\n<p><strong>Issue 8<\/strong><\/p>\n<pre><code class=\"cs\">public GenericCollectionHelper(IntermediateSerializer serializer,                                Type type) {   var collectionElementType = GetCollectionElementType(type, false);   _contentSerializer =                  serializer.GetTypeSerializer(collectionElementType);   .... } <\/code><\/pre>\n<p>PVS-Studio warning:<a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3080\/\"> V3080<\/a> Possible null dereference inside method at &#8216;type.IsArray&#8217;. Consider inspecting the 1st argument: collectionElementType. MonoGame.Framework.Content.Pipeline GenericCollectionHelper.cs 48<\/p>\n<p>PVS-Studio indicates that <em>collectionElementType<\/em> is passed to the <em>serializer.GetTypeSerializer<\/em> method. <em>collectionElementType<\/em> may be <em>null<\/em>. This argument is dereferenced inside of the method, and this is another potential <em>NullReferenceException<\/em>.<\/p>\n<p>Let&#8217;s check that we cannot pass <em>null<\/em> to <em>ContentTypeSerializer:<\/em><\/p>\n<pre><code class=\"cs\">public ContentTypeSerializer GetTypeSerializer(Type type) {   ....   if (type.IsArray)   {     ....   }   .... } <\/code><\/pre>\n<p>Note that if the <em>type<\/em> parameter is <em>null<\/em>, then accessing <em>IsArray<\/em> property will throw an exception.<\/p>\n<p>Passed <em>collectionElementType<\/em>is initialized with the return value of the <em>GetCollectionElementType<\/em> method. Let&#8217;s look at what this method has inside:<\/p>\n<pre><code class=\"cs\">private static Type GetCollectionElementType(Type type,                                              bool checkAncestors) {   if (!checkAncestors        &amp;&amp; type.BaseType != null        &amp;&amp; FindCollectionInterface(type.BaseType) != null)     return null;    var collectionInterface = FindCollectionInterface(type);   if (collectionInterface == null)     return null;    return collectionInterface.GetGenericArguments()[0]; } <\/code><\/pre>\n<p>If the control switches to one of the two conditional constructions, <em>null<\/em> will be returned. Two scenarios that lead to <em>NullReferenceException<\/em> versus one scenario that leads to non-<em>null<\/em> value returned. Still, not a single check.<\/p>\n<p><strong>Issue 9<\/strong><\/p>\n<pre><code class=\"cs\">class Floor0 : VorbisFloor {   int _rate;   ....   int[] SynthesizeBarkCurve(int n)   {     var scale = _bark_map_size \/ toBARK(_rate \/ 2);     ....   } } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3041\/\">V3041<\/a> The expression was implicitly cast from &#8216;int&#8217; type to &#8216;double&#8217; type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) \/ Y;. MonoGame.Framework.DesktopGL(netstandard2.0) VorbisFloor.cs 113<\/p>\n<p>The analyzer warns that when the integer value of <em>_rate<\/em> is divided by two, an unexpected loss of the fractional part of the result may occur. This is a warning from the NVorbis code.<\/p>\n<p>The warning relates to the second division operator. The <em>toBARK<\/em> method signature looks like this:<\/p>\n<pre><code class=\"cs\">static float toBARK(double lsp) <\/code><\/pre>\n<p>The <em>_rate<\/em> field has the <em>int<\/em> type. The result of division an integer type variable by a same-type variable is also an integer \u2013 the fractional part will be lost. If this behavior was not intended, then to get a <em>double<\/em> value as a result of division, you can, for example, add the <em>d<\/em> literal to a number or write this number with a dot:<\/p>\n<pre><code class=\"cs\">var scale = _bark_map_size \/ toBARK(_rate \/ 2d); var scale = _bark_map_size \/ toBARK(_rate \/ 2.0); <\/code><\/pre>\n<p><strong>Issue 10<\/strong><\/p>\n<pre><code class=\"cs\">internal int InflateFast(....) {   ....   if (c > e)   {     \/\/ if source crosses,     c -= e; \/\/ wrapped copy     if (q - r > 0 &amp;&amp; e > (q - r))     {       do       {         s.window[q++] = s.window[r++];       }       while (--e != 0);     }     else     {       Array.Copy(s.window, r, s.window, q, e);       q += e; r += e; e = 0;    \/\/ &lt;=     }     r = 0; \/\/ copy rest from start of window    \/\/ &lt;=   }   .... } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3008\/\">V3008<\/a> The &#8216;r&#8217; variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 1309, 1307. MonoGame.Framework.DesktopGL(netstandard2.0) Inflate.cs 1309<\/p>\n<p>The analyzer detected that a variable with a value was assigned a new value. The previous one was never used. This warning was issued on the DotNetZip code.<\/p>\n<p>If the control moves to the <em>else<\/em> branch, the <em>r<\/em> variable is assigned the sum of <em>r<\/em> and <em>e<\/em>. When the branch exits, the first operation will assign another value to <em>r<\/em>, without using the current one. The sum will be lost, making part of the calculations meaningless.<\/p>\n<h3>Conclusion<\/h3>\n<p>Errors can be different. Even skilled developers make them. In this article we inspected both simple mistakes and dangerous fragments. The developers may not even notice some of them \u2014 code doesn&#8217;t always say that one method returns <em>null<\/em> and the other method uses this <em>null<\/em> without any check.<\/p>\n<p>Static analysis isn&#8217;t perfect, but it still finds errors like these (and many more!). So why don&#8217;t you <a href=\"https:\/\/pvs-studio.com\/pvs-studio\/try-free\/?utm_source=habr&amp;utm_medium=articles&amp;utm_content=monogame&amp;utm_term=link_try-free\">try the analyzer<\/a> and check your projects? Maybe you&#8217;ll find some interesting things too.<\/p>\n<p>Thank you and see you in next articles!<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/649771\/\"> https:\/\/habr.com\/ru\/articles\/649771\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>The PVS-Studio analyzer often checks code of libraries, frameworks, and engines for game development. Today we check another project \u2014 MonoGame, a low-level gamedev framework written in C#.<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<h3>Introduction<\/h3>\n<p>MonoGame is an open-source framework for game development. It&#8217;s the heir of the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Microsoft_XNA\">XNA<\/a> project, which was developed by Microsoft until 2013.<\/p>\n<p>Let me also remind you about what <a href=\"https:\/\/pvs-studio.com\/en\/pvs-studio\/\">PVS-Studio<\/a> is :). PVS-Studio is a static code analyzer that searches for various code errors and security-related vulnerabilities. I used PVS-Studio version 7.16 and <a href=\"https:\/\/github.com\/MonoGame\/MonoGame\">MonoGame sources<\/a> from 12.01.2022.<\/p>\n<p>It&#8217;s worth mentioning that the analyzer issued a couple of warnings on some libraries used in the project \u2014 DotNetZip and NVorbis. I described them below. If you want, you can easily <a href=\"https:\/\/pvs-studio.com\/en\/docs\/manual\/0014\/\">exclude third-party code<\/a> from your analysis.<\/p>\n<h3>Analyzer warnings<\/h3>\n<p><strong>Issue 1<\/strong><\/p>\n<pre><code class=\"cs\">public void Apply3D(AudioListener listener, AudioEmitter emitter)  {   ....   var i = FindVariable(\"Distance\");   _variables[i].SetValue(distance);   ....   var j = FindVariable(\"OrientationAngle\");   _variables[j].SetValue(angle);   .... } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3106\/\">V3106<\/a> Possible negative index value. The value of &#8216;i&#8217; index could reach -1. MonoGame.Framework.DesktopGL(netstandard2.0) Cue.cs 251<\/p>\n<p>The analyzer noticed that the <em>i<\/em> variable can have value -1. This variable was used as an index.<\/p>\n<p>The <em>i<\/em> variable is initialized by the return value of the <em>FindVariable<\/em> method. Let&#8217;s look inside this method:<\/p>\n<pre><code class=\"cs\">private int FindVariable(string name) {   \/\/ Do a simple linear search... which is fast   \/\/ for as little variables as most cues have.   for (var i = 0; i &lt; _variables.Length; i++)   {     if (_variables[i].Name == name)     return i;   }    return -1; } <\/code><\/pre>\n<p>If no element with the corresponding value in the collection is found, then the return value is -1. Obviously, using a negative number as an index will lead to <em>IndexOutOfRangeException<\/em>.<\/p>\n<p><strong>Issue 2<\/strong><\/p>\n<p>The next problem was also found in the <em>Apply3D<\/em> method:<\/p>\n<pre><code class=\"cs\">public void Apply3D(AudioListener listener, AudioEmitter emitter) {   ....   lock (_engine.UpdateLock)   {     ....     \/\/ Calculate doppler effect.     var relativeVelocity = emitter.Velocity - listener.Velocity;     relativeVelocity *= emitter.DopplerScale;   } } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3137\/\">V3137<\/a> The &#8216;relativeVelocity&#8217; variable is assigned but is not used by the end of the function. MonoGame.Framework.DesktopGL(netstandard2.0) Cue.cs 266<\/p>\n<p>The analyzer warns us that the value was assigned, but never used further.<\/p>\n<p>Someone might get confused by the fact that the code is in the <em>lock<\/em> block, but&#8230; It means nothing for <em>relativeVelocity<\/em> because this variable is declared locally and doesn&#8217;t participate in the inter-thread communication.<\/p>\n<p>Maybe the value of <em>relativeVelocity<\/em> should be assigned to a field.<\/p>\n<p><strong>Issue 3<\/strong><\/p>\n<pre><code class=\"cs\">private void SetData(int offset, int rows, int columns, object data) {   ....   if(....)   {     ....   }   else if (rows == 1 || (rows == 4 &amp;&amp; columns == 4))    {     \/\/ take care of shader compiler optimization     int len = rows * columns * elementSize;     if (_buffer.Length - offset > len)           len = _buffer.Length - offset;    \/\/  &lt;=     Buffer.BlockCopy(data as Array,                      0,                      _buffer,                      offset,                      rows*columns*elementSize);   }   .... } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3137\/\">V3137<\/a> The &#8216;len&#8217; variable is assigned but is not used by the end of the function. MonoGame.Framework.DesktopGL(netstandard2.0) ConstantBuffer.cs 91<\/p>\n<p>Another warning about a value assigned but never used.<\/p>\n<p>The <em>len<\/em> variable is initialized with the following expression:<\/p>\n<pre><code class=\"cs\">int len = rows * columns * elementSize; <\/code><\/pre>\n<p>If you look closely at the code, you might feel deja vu, because this expression repeats one more time:<\/p>\n<pre><code class=\"cs\">Buffer.BlockCopy(data as Array, 0,                  _buffer,                  offset,                  rows*columns*elementSize);    \/\/ &lt;= <\/code><\/pre>\n<p>Most likely, <em>len<\/em> was supposed to be in this place.<\/p>\n<p><strong>Issue 4<\/strong><\/p>\n<pre><code class=\"cs\">protected virtual object EvalSampler_Declaration(....) {   if (this.GetValue(tree, TokenType.Semicolon, 0) == null)     return null;            var sampler = new SamplerStateInfo();   sampler.Name = this.GetValue(tree, TokenType.Identifier, 0) as string;   foreach (ParseNode node in nodes)     node.Eval(tree, sampler);            var shaderInfo = paramlist[0] as ShaderInfo;   shaderInfo.SamplerStates.Add(sampler.Name, sampler);    \/\/ &lt;=            return null; } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3156\/\">V3156<\/a> The first argument of the &#8216;Add&#8217; method is not expected to be null. Potential null value: <a href=\"http:\/\/sampler.Name\">sampler.Name<\/a>. MonoGame.Effect.Compiler ParseTree.cs 1111<\/p>\n<p>The analyzer warns us that the <em>Add<\/em> method is not designed to take <em>null<\/em> as a first argument. At the same time the analyzer warns us that the first argument <a href=\"http:\/\/sampler.Name\"><em>sampler.Name<\/em><\/a>, passed to <em>Add<\/em>, can be <em>null<\/em>.<\/p>\n<p>To begin with, let&#8217;s look at the <em>shaderInfo.SamplerStates<\/em> field:<\/p>\n<pre><code class=\"cs\">public class ShaderInfo {   ....    public Dictionary&lt;string, SamplerStateInfo> SamplerStates =      new Dictionary&lt;string, SamplerStateInfo>(); } <\/code><\/pre>\n<p>It&#8217;s a dictionary and <em>Add<\/em> is a standard method. Indeed, <em>null<\/em> cannot be a dictionary key.<\/p>\n<p>The value of the <a href=\"http:\/\/sampler.Name\"><em>sampler.Name<\/em><\/a> field is passed as the dictionary key. A potential <em>null<\/em> can be assigned in this line:<\/p>\n<pre><code class=\"cs\">sampler.Name = this.GetValue(tree, TokenType.Identifier, 0) as string; <\/code><\/pre>\n<p>The <em>GetValue<\/em> method can return <em>null<\/em> or an instance of any type other than <em>string<\/em>. Thus, the result of casting via the <em>as<\/em> operator is <em>null<\/em>.  Could it be? Let&#8217;s look at <em>getValue<\/em>:<\/p>\n<pre><code class=\"cs\">protected object GetValue(ParseTree tree,                           TokenType type,                           ref int index) {   object o = null;   if (index &lt; 0) return o;    \/\/ left to right   foreach (ParseNode node in nodes)   {     if (node.Token.Type == type)     {       index--;       if (index &lt; 0)       {         o = node.Eval(tree);         break;       }     }   }   return o; } <\/code><\/pre>\n<p>So, this method can return <em>null<\/em> in two cases:<\/p>\n<ol>\n<li>\n<p>If the passed <em>index<\/em> value is less than 0;<\/p>\n<\/li>\n<li>\n<p>If an element of the <em>nodes<\/em> collection that matches the passed <em>type<\/em> was not found.<\/p>\n<\/li>\n<\/ol>\n<p>The developer should have added <em>null<\/em> check for the return value of the <em>as<\/em> operator.<\/p>\n<p><strong>Issue 5<\/strong><\/p>\n<pre><code class=\"cs\">internal void Update() {   if (GetQueuedSampleCount() > 0)   {     BufferReady.Invoke(this, EventArgs.Empty);   } } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3083\/\">V3083<\/a> Unsafe invocation of event &#8216;BufferReady&#8217;, NullReferenceException is possible. Consider assigning event to a local variable before invoking it. MonoGame.Framework.DesktopGL(netstandard2.0) Microphone.OpenAL.cs 142<\/p>\n<p>The analyzer warns about an unsafe invocation of event that potentially has no subscribers.<\/p>\n<p>Before the event invocation, the return value of the <em>GetQueuedSampleCount<\/em> method is checked. If the presence of subscribers to the event does not depend on the truth of the condition, then a <em>NullReferenceException<\/em> may be thrown when this event is called.<\/p>\n<p>If the truth of the expression &#171;<em>GetQueuedSampleCount() > 0><\/em>&#187; guarantees the presence of subscribers, the problem still remains. The state can change between the check and the invocation. The <em>BufferReady<\/em> event is declared like this:<\/p>\n<pre><code class=\"cs\">public event EventHandler&lt;EventArgs> BufferReady; <\/code><\/pre>\n<p>Note that the <em>public<\/em> access modifier allows other developers to use the <em>BufferReady<\/em> event in any code. This increases the chance of performing operations with the event in other threads.<\/p>\n<p>Thus, adding <em>null<\/em> check in the condition does not prevent from <em>NullReferenceException<\/em>, because the <em>BufferReady<\/em> state can change between the check and the invocation.<\/p>\n<p>The easiest way to fix it is to add Elvis operator &#8216;?.&#8217; to the <em>Invoke<\/em> call:<\/p>\n<pre><code class=\"cs\">BufferReady?.Invoke(this, EventArgs.Empty); <\/code><\/pre>\n<p>If this option is not available for some reason, assign <em>BufferReady<\/em> to a local variable and work with it:<\/p>\n<pre><code class=\"cs\">EventHandler&lt;EventArgs> bufferReadyLocal = BufferReady; if (bufferReadyLocal != null)   bufferReadyLocal.Invoke(this, EventArgs.Empty); <\/code><\/pre>\n<p>Errors with <em>public<\/em> events in multi-threaded code may appear rarely, but they are very malicious. These errors are hard or even impossible to reproduce. You can read more about safer work with operators in the <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3083\/\">V3083<\/a> documentation.<\/p>\n<p><strong>Issue 6<\/strong><\/p>\n<pre><code class=\"cs\">public override TOutput Convert&lt;TInput, TOutput>(   TInput input,   string processorName,   OpaqueDataDictionary processorParameters) {   var processor = _manager.CreateProcessor(processorName,                                                  processorParameters);   var processContext = new PipelineProcessorContext(....);   var processedObject = processor.Process(input, processContext);   .... } <\/code><\/pre>\n<p>PVS-Studio warning: <a href=\"https:\/\/pvs-studio.com\/en\/docs\/warnings\/v3080\/\">V3080<\/a> Possible null dereference. Consider inspecting &#8216;processor&#8217;. MonoGame.Framework.Content.Pipeline PipelineProcessorContext.cs 55<\/p>\n<p>The analyzer warns about possible dereference of the null reference when <em>processor.Process<\/em> is called.<\/p>\n<p>An object of the <em>processor<\/em> class is created via the <em>_manager.CreateProcessor<\/em> call. Let&#8217;s look at its code fragment:<\/p>\n<pre><code class=\"cs\">public IContentProcessor CreateProcessor(                     string name,                     OpaqueDataDictionary processorParameters) {   var processorType = GetProcessorType(name);   if (processorType == null)     return null;   .... } <\/code><\/pre>\n<p>We see that <em>CreateProcessor<\/em> returns <em>null<\/em> if <em>GetProcessorType<\/em> also returns <em>null<\/em>. Well, let&#8217;s look at the method&#8217;s code:<\/p>\n<pre><code class=\"cs\">public Type GetProcessorType(string name) {   if (_processors == null)     ResolveAssemblies();    \/\/ Search for the processor type.   foreach (var info in _processors)   {     if (info.type.Name.Equals(name))       return info.type;   }    return null; } <\/code><\/pre>\n<p>This method can return <em>null<\/em> if no matching element was found in the collection. If <em>GetProcessorType<\/em> returns <em>null<\/em>, then <em>CreateProcessor<\/em> also returns <em>null<\/em>, which will be written to the <em>processor<\/em> variable. As a result, <em>NullReferenceException<\/em> will be thrown if we call the <em>processor.Process<\/em> method.<\/p>\n<p>Let&#8217;s go back to the <em>Convert<\/em> method from the warning. Have you noticed that it has the <em>override<\/em> modifier? This method is an implementation of a contract from an abstract class. Here&#8217;s this abstract method:<\/p>\n<pre><code class=\"cs\">\/\/\/ &lt;summary> \/\/\/ Converts a content item object using the specified content processor. \/\/\/.... \/\/\/ &lt;param name=\"processorName\">Optional processor  \/\/\/ for this<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-387889","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/387889","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=387889"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/387889\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=387889"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=387889"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=387889"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}