Checking the source code for WPF Samples from Microsoft

    In order to popularize the PVS-Studio code analyzer, which learned to check, in addition to C ++, C # projects, we decided to check the WPF source code of the examples offered by Microsoft.



    With the release of Windows Vista, a new system was introduced for building beautiful client applications - the Windows Presentation Foundation (WPF). This graphics subsystem has been included in the .NET Framework since version 3.0. It uses the XAML markup language and has replaced the outdated WinForms. In my opinion, the main drawback of WinForms was that it carried out all the drawing on the central processor. WPF acted more logically and rendered drawing its DirectX components. Now WPF has almost superseded WinForms and allows you to make universal interfaces for three platforms at once (PC, XBOXOne, Winphone).

    To analyze the source code of the WPF examples from Microsoft ( source ), we used the PVS-Studio version 6.05 static code analyzer .

    An interesting feature of this Solution is that along with C # projects it contains projects written in C ++. I learned about this feature only from the list of errors found by PVS-Studio. The PVS-Studio plugin for Visual Studio, without any additional manipulations from the user, was analyzed and displayed in messages related to both C # and C ++ code.

    Figure 1. The PVS-Studio window simultaneously displays warnings related to both C # and C ++ code of the verified project.


    Figure 1. The PVS-Studio window simultaneously displays warnings related to both C # and C ++ code of the tested project (click on the picture to enlarge).

    C # Errors


    1. Errors when compiling the conditions of an if statement

    Errors when comparing something with something are a very common problem among programmers. Let's go over them.

    There are two absolutely identical conditions in this source code:
    public int Compare(GlyphRun a, GlyphRun b)
    {
      ....
      if (aPoint.Y > bPoint.Y) //<==
      {
        return -1;
      }
      else if (aPoint.Y > bPoint.Y) //<==
      {
        result = 1;
      }
      else if (aPoint.X < bPoint.X)
      {
        result = -1;
      }
      else if (aPoint.X > bPoint.X)
      {
        result = 1;
      }
      ....
    }

    V3003 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 418, 422. txtserializerwriter.cs 418

    What exactly they wanted to compare in the second case is not entirely clear, but clearly not what they wrote.

    We write polls for null without a trace in the conditions, than we protect the program from emergency situations. It can be said that most of the if conditions are precisely checks for null of any fields or variables. At the same time, sometimes checks are not only unnecessary, but can also carry a logical error:
    public static string FindNumeric(string content)
    {
      string[] values = content.Split(' ');
      if (values != null)
      {
        return values[0];
      }
      return "none";
    }

    V3022 Expression 'values! = Null' is always true. Util.cs 287

    We can assume that the author wanted to check that values contains more than 0 elements, but I could not think of a situation when Split returns an empty array. In any case, whatever one may say, checking for null here was completely superfluous.

    As I said, the project contains code from C ++ and C # diagnostics. I got the impression that it was the C ++ programmer who had a hand in the next code.
    private void LoadNotes()
    {
      var fs = new FileStream("NotesFile", FileMode.Open);
      if (fs != null)
      {
        ....
    }

    V3022 Expression 'fs! = Null' is always true. MainWindow.cs 66

    In fact, even in C ++ code this option is erroneous, but in C # it looks “strange” in general. Why it is so incorrect to write in C ++ code is described in the article " Checking the source code of 7-Zip using PVS-Studio ", well, but we will continue to analyze C # code.

    We could not get away from such situations. Solution found two almost identical functions (thanks to “copy-paste”) with the same error:
    private void SerializeObjectTree(object objectTree)
    {
      TextWriter writer = new StreamWriter(_stream);
      try
      {
        string fileContent =
         XamlRtfConverter.ConvertXamlToRtf(
             XamlWriter.Save(objectTree));
        writer.Write(fileContent);
      }
      finally
      {
        if (writer != null)
          writer.Close();
      }
    }

    V3022 Expression 'writer! = Null' is always true. htmlserializerwriter.cs 324

    Well, a writer will not be a null reference ...

    Throwing an error in exceptional situations is not a bad decision. The main thing is not to make a mistake in the condition when you need to throw an exception, because you can create a very unpleasant impression on the user of your application when the program crashes out of the blue.
    protected Size SizeParser(string o)
    {
      ....
      if (sizeString.Length == 0 || sizeString.Length != 2)
       throw new ApplicationException("Error: a size should 
               contain two double that seperated by a space 
               or ',' or ';'");
      ....
    }

    V3023 Consider inspecting the 'sizeString.Length == 0 || sizeString.Length! = 2 'expression. The expression is excessive or contains a misprint. MainWindow.cs 140

    Judging by the text of the error, in this case, checking for 0 is unnecessary, it was enough to check sizeString.Length for inequality 2.

    In the long bodies of the if statement, it is very difficult to detect meaningless checks with the eyes.
    private static void WriteElement(....)
    {
      if (htmlWriter == null)
      {
        ....
      }
      else
      {
         if (htmlWriter != null && htmlElementName != null)
         {
           ....
      ....
    }

    V3063 A part of conditional expression is always true: htmlWriter! = Null. HtmlFromXamlConverter.cs 491

    This is not a problem for the analyzer. By the way, thanks to our favorite copy-paste, the error was found in two projects at once: HtmlToXamlDemo and DocumentSerialization .

    Of course, meaningless checks are found not only in long functions, but also within two or three lines.
    private void OnFlipPicTimeline(object sender, EventArgs e)
    {
      var clock = (Clock) sender;
      if (clock.CurrentState == ClockState.Active) // Begun case
      {
        return;
      }
      if (clock.CurrentState != ClockState.Active) // Ended case
      {
        ....
      }
    }

    V3022 Expression 'clock.CurrentState! = ClockState.Active' is always true. MainWindow.cs 103

    In principle, it’s okay, but when the if statement comes nested in one more if and another and another ... I want to get rid of at least meaningless checks for a better perception of the code, which, in fact, is read much more than it is written .

    Let's take a break. And look at this unusual function that I met. The body of the function is given in full:
    private void UpdateSavings()
    {
      Savings = TotalIncome - (Rent + Misc + Food);
      if (Savings < 0)
      {
      }
      else if (Savings >= 0)
      {
      }
    }

    V3022 Expression 'Savings> = 0' is always true. NetIncome.cs 98

    Also, many (over 60) comparisons of real numbers ( double ) with a specific 0 were found.
    if (leftConst != null && leftConst.Value == 0)
    {
      // 0 + y;  return y;
      return newRight;
    }

    For example:
    • V3024 An odd precise comparison: leftConst.Value == 0. Consider using a comparison with defined precision: Math.Abs ​​(A - B) <Epsilon. AddExpression.cs 34
    • V3024 An odd precise comparison: leftConst.Value == 1. Consider using a comparison with defined precision: Math.Abs ​​(A - B) <Epsilon. MultExpression.cs 42
    • V3024 An odd precise comparison: leftConst.Value == -1. Consider using a comparison with defined precision: Math.Abs ​​(A - B) <Epsilon. MultExpression.cs 47
    • etc...

    Articles are not enough to bring all the lines. This warning is at the 3rd level, because its relevance greatly depends on the specifics of the program. If mathematical calculations are performed on numbers (manipulating a value), it is not guaranteed that we will get a specific number: -1, 0, 1. A deviation of 0.00000000001 will already lead to an incorrect comparison result. However, if the logic of the program involves writing discrete values into real numbers ( double ), then such checks are not an error.

    2. Errors in initialization and assignment of variables

    A function is a great thing, which not only allows you to remove duplicate code, but also simplify the understanding of the section of code where this function is used. It is especially important that the function performs exactly the task that is described in its name and call signature. But this does not always happen. Take, for example, the following section of code. The function is provided completely for a better understanding of the situation.
    public bool OpenDocument(string fileName)
    {
      Microsoft.Win32.OpenFileDialog dialog;
      // If there is a document currently open, close it.
      if (this.Document != null)  CloseFile();
      dialog = new Microsoft.Win32.OpenFileDialog();
      dialog.CheckFileExists = true;
      dialog.InitialDirectory = GetContentFolder();
      dialog.Filter = this.OpenFileFilter;
      bool result = (bool)dialog.ShowDialog(null);
      if (result == false)  return false;
      fileName = dialog.FileName; //<==
      return OpenFile(fileName);
    }

    V3061 Parameter 'fileName' is always rewritten in method body before being used. ThumbViewer.xaml.cs 192

    The name of the file that you intend to open is milled right before its first use fileName = dialog.FileName . Yes, in this case, a dialog box will open and a user file will be selected, but why then do you need a parameter that is not actually used?

    Lack of time and copy-paste sometimes give rise to very strange designs:
    public MailSettingsDialog()
    {
      ....
      _timerClock = _timerClock = new DispatcherTimer(); 
      ....
    }

    V3005 The '_timerClock' variable is assigned to itself. MailSettingsDialog.cs 56

    It would seem that this is not a terrible typo, but it raises the idea of ​​whether we “write for the second time” there. Well, for example, like here:
    private void DumpAllClipboardContentsInternal()
    { 
      ....
      if (dataObject == null)
      {
        clipboardInfo.Text =
          clipboardInfo.Text =
            "Can't access clipboard now! 
              \n\nPlease click Dump All Clipboard 
                  Contents button again.";
      } 
      else 
      {
         ....
    }

    V3005 The 'clipboardInfo.Text' variable is assigned to itself. MainWindow.cs 204

    Generally, the code is full of strange assignments:
    private void DoParse(string commandLine)
    {
      ....
      strLeft = strRight = string.Empty;
      strLeft = strs[0];
      strRight = strs[1];
      ....
    }

    V3008 The 'strLeft' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 55, 54. CommandLine.cs 55

    V3008 The 'strRight' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 56, 54. CommandLine.cs 56

    strLeft and strRight are just local variables of type string .

    Well or here, the further code sample is more incorrect. For some reason, there were so many things counted in it, and then they were counted again and recorded in the same variable.
    private object InvokMethod(....)
    {
      arg = commandLine.Substring(
        commandLine.IndexOf("(", StringComparison.Ordinal) + 1,
          commandLine.IndexOf(")", 
            StringComparison.Ordinal) - 
            (commandLine.IndexOf("(", 
              StringComparison.Ordinal) + 1));
      arg = commandLine.Substring(
        commandLine.IndexOf("(", 
          StringComparison.Ordinal) + 1);
    }

    V3008 The 'arg' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 176, 173. CommandLine.cs 176

    And many, many more such or similar examples of meaningless primary assignment:
    private void DrawFormattedText(DpiScale dpiInfo)
    {
      ....
      Geometry geometry = new PathGeometry();
      geometry = formattedText.BuildGeometry(
         new System.Windows.Point(0, 0));
      ....
    }
    • V3008 The 't' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 141, 115. TrackBall.cs 141
    • V3008 The 't' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 141, 115. TrackBall.cs 141
    • V3008 The 'columnSpan' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 2115, 2101. HtmlToXamlConverter.cs 2115
    • V3008 The '_timerInterval' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 52, 47. ClientForm.cs 52
    • V3008 The 'matrix1' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 126, 125. MainWindow.cs 126
    • V3008 The 'matrixResult' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 140, 138. MainWindow.cs 140
    • V3008 The 'matrixResult' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 351, 349. MainWindow.cs 351
    • V3008 The 'matrixResult' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 369, 367. MainWindow.cs 369
    • V3008 The 'pointResult' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 480, 478. MainWindow.cs 480
    • V3008 The 'columnSpan' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 1930, 1917. htmltoxamlconverter.cs 1930
    • V3008 The 'geometry' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 56, 55. MainWindow.xaml.cs 56
    • V3008 The 'pathGeometry' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 66, 65. MainWindow.xaml.cs 66

    It does not make sense to write each example, all the more so many important and serious mistakes await us.

    3. A couple of heterogeneous errors

    Throwing exceptions, it is important to keep the call stack, so that later on the logs to understand - "what the user basically fell." Not everyone knows how to do it right.
    public static object InvokePropertyOrMethod(....)
    {
      try
      {
         ....
      }
      catch (MissingMethodException e)
      {
        ....
        throw e;
      }
      catch (AmbiguousMatchException e)
      {
         throw e;
      }
      return resultObject;
    }

    V3052 The original exception object 'e' was swallowed. Stack of original exception could be lost. ReflectionUtils.cs 797

    V3052 The original exception object 'e' was swallowed. Stack of original exception could be lost. ReflectionUtils.cs 806

    According to the standard, if you pass exception up the function call stack by throw e; , we will lose the call stack that was before the exception was caught in the catch block . To save the entire call stack and continue it, you just need to write one throw word in the catch block and that’s it.

    Sometimes checks are superfluous, and sometimes they are simply not enough, as for example in the following code:
    private static void ParseCssFontFamily(....)
    {
      ....
      if (fontFamilyList == null && fontFamily.Length > 0)
      {
        if (fontFamily[0] == '"' || fontFamily[0] == '\'')
        {
          // Unquote the font family name
          fontFamily = 
            fontFamily.Substring(1, fontFamily.Length - 2);
          ....
    }

    V3057 The 'Substring' function could receive the '-1' value while non-negative value is expected. Inspect the second argument. HtmlCSSParser.cs 645

    There is no verification that fontFamily.Length is greater than 1, and as a result of subtracting 2 from fontFamily.Length , we can get a value less than 0. And this function throws an ArgumentOutOfRangeException in such cases .

    It would be safer to write a check:
    if (fontFamilyList == null && fontFamily.Length > 1)

    4. WPF error

    DependencyProperty is one of the most remarkable features of WPF. Creating properties that right out of the box can notify the developer of their change is incredibly convenient. But the main thing is not to confuse the signature for their description, it is especially important to remember this when showing examples, because it is precisely on them that people are oriented.
    public double Radius
    {
      get { return (double) GetValue(RadiusProperty); }
      set { SetValue(RadiusProperty, value); }
    }
    public static readonly DependencyProperty 
      RadiusProperty = DependencyProperty.Register(
        "RadiusBase",
        typeof (double),
        typeof (FireworkEffect),
        new FrameworkPropertyMetadata(15.0));

    V3045 WPF: the names of the registered property 'RadiusBase', and of the property 'Radius', do not correspond with each other. FireworkEffect.cs 196

    In this particular case, the name that is registered for the dependency property does not match the name of the wrapper property to access with DependencyProperty from the code. This option leads to big problems when working from XAML markup. WPF allows you to access the simple Radius property from XAML and read the value from there, but changes to this property will not be picked up from XAML.

    In fact, in PVS-Studio there are a number of diagnostics for detecting errors in the signature of the creation of DependencyProperty [ 3044 , 3045 , 3046, 3047 , 3048 , 3049 ]. Most errors of this kind lead to a program crash as soon as the use of a class with these dependency properties begins. Therefore, the diagnostic data is intended precisely to save you from searching and analyzing long footsteps of signatures, especially after copying. To do this, of course, you need regular verification of the code, and not just analysis of the final version of the program.

    Let's consider one more interesting operation. In this case, the new V3095 diagnostic worked. This diagnostic shows the places where we first access the variable, and then check it for null .
    private static XmlElement AddOrphanListItems(....)
    {
      Debug.Assert(htmlLiElement.LocalName.ToLower() == "li");
      ....
      XmlNode htmlChildNode = htmlLiElement;
      var htmlChildNodeName = htmlChildNode == null 
          ? null 
          : htmlChildNode.LocalName.ToLower();
      ....
    }

    V3095 The 'htmlLiElement' object was used before it was verified against null. Check lines: 916, 936. HtmlToXamlConverter.cs 916

    In this case, under the condition of the ternary operator, we check that the htmlChildNode variable can be null . Moreover, the htmlChildNode variable is nothing more than a reference to the htmlLiElement variable . But it was the htmlLiElement variable that we accessed without checking for null . As a result, we either have code that will never execute, or we will get a NullReferenceException in the htmlLiElement.LocalName.ToLower () line .

    In addition to the errors described, a lot of attention is attracted to the diagnostics under number V3072 , which is designed to detect the presence of fields with a type that implements the IDisposable interface , but the class itself, where the fields are declared, does not have such an implementation.
    internal class Email
    {
      private readonly SmtpClient _client;
      ....
    }

    V3072 The 'Email' class containing IDisposable members does not itself implement IDisposable. Inspect: _client. Email.cs 15

    C IDisposable has always been tight. From critical errors, due to improper use of it, it’s finished, often Finalize saves , well, at least in standard classes. Programmers often clog, forget, miss, or simply do not pay attention to fields with a type that implements this interface. To justify such a code or admit the presence of an error in it is difficult for a third-party look, but there are some patterns that are worth paying attention to. In this Solution, there were not a few such triggers.
    • V3072 The 'HtmlLexicalAnalyzer' class containing IDisposable members does not itself implement IDisposable. Inspect: _inputStringReader. HtmlLexicalAnalyzer.cs 16
    • V3072 The 'MainWindow' class containing IDisposable members does not itself implement IDisposable. Inspect: _customersTableAdapter, _nwDataSet ... MainWindow.cs 15
    • V3072 The 'MainWindow' class containing IDisposable members does not itself implement IDisposable. Inspect: _listControl. MainWindow.cs 14
    • V3072 The 'ThumbViewer' class containing IDisposable members does not itself implement IDisposable. Inspect: _annStore, _annotationBuffer. ThumbViewer.xaml.cs 31
    • V3072 The 'HtmlLexicalAnalyzer' class containing IDisposable members does not itself implement IDisposable. Inspect: _inputStringReader. htmllexicalanalyzer.cs 24
    • V3072 The 'MainWindow' class containing IDisposable members does not itself implement IDisposable. Inspect: _store. MainWindow.cs 20
    • V3072 The 'MainWindow' class containing IDisposable members does not itself implement IDisposable. Inspect: _customCursor. MainWindow.cs 14
    • V3072 The 'MainWindow' class containing IDisposable members does not itself implement IDisposable. Inspect: _speechSynthesizer. MainWindow.cs 14

    C ++ Errors


    1. Errors in drafting the conditions of the if statement

    For me, of course, it was a revelation that I would find C ++ projects in this Solution, but nevertheless, there are errors and let's look at them.

    As in C #, we start with various comparisons and immediately look at the very C ++ error that I mentioned a little higher in the C # block.
    STDMETHOD(CreateInstance)(....)
    {
      ....
      T *obj = new T();
      if (NULL != obj)
      {
        ....
    }

    V668 There is no sense in testing the 'obj' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. classfactory.h 76

    If the new operator could not allocate memory, then, according to the C ++ language standard, an exception std :: bad_alloc () is thrown. Thus, it does not make sense to check for equality to zero, because the obj pointer will never be NULL . If memory cannot be allocated, an exception occurs that can be handled at a higher level, and a check for NULL equality can simply be deleted. If exceptions in the application are undesirable, then you can use the new operatorthat does not throw exceptions ( T * obj = new (std :: nothrow) T () ): in this case, you can check the return value to zero. There were 4 more similar checks in Solution:
    • V668 There is no sense in testing the 'colors' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. aitdecoder.cpp 182
    • V668 There is no sense in testing the 'pixels' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. aitencoder.cpp 157
    • V668 There is no sense in testing the 'colors' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. aitencoder.cpp 221
    • V668 There is no sense in testing the 'bytes' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. aitencoder.cpp 275

    Extra conditions are relevant in both programming languages:
    if (bitmapLock && bitmap)
    {
      if(bitmapLock)
      {
        bitmapLock->Release();
        bitmapLock = NULL;
      }
    }

    V571 Recurring check. The 'bitmapLock' condition was already verified in line 104. aitdecoder.cpp 106

    Some C # programmers do not know that the following two operations on a Nullable type are equivalent:
    • _isInDesignMode! = null
    • _isInDesignMode.HasValue

    And they do similar checks:
    if (_isInDesignMode != null && _isInDesignMode.HasValue)

    In C ++, they like to pointlessly check for a pointer to null before freeing up memory allocated at the address to which it points.
    static HRESULT OutputColorContext(....)
    {
      ....
      if (pixels)
        delete[] pixels;
      ....
    }

    V809 Verifying that a pointer value is not NULL is not required. The 'if (pixels)' check can be removed. aitencoder.cpp 189
    static HRESULT OutputBitmapPalette(....)
    {
      ....
      if (colors)
        delete[] colors;
      ....
    }

    V809 Verifying that a pointer value is not NULL is not required. The 'if (colors)' check can be removed. aitencoder.cpp 241
    static HRESULT OutputColorContext(....)
    {
      if (bytes)
        delete[] bytes;
    }

    V809 Verifying that a pointer value is not NULL is not required. The 'if (bytes)' check can be removed. aitencoder.cpp 292

    2. Logical error

    The following code presents a very interesting situation of logical comparison, although at first glance you will not say so:
    STDMETHODIMP AitDecoder::QueryCapability(....)
    {
      ....
      // If this is our format, we can do everything
      if (strcmp(bh.Name, "AIT") == 0)
      {
         *pCapability = 
           WICBitmapDecoderCapabilityCanDecodeAllImages ||
           WICBitmapDecoderCapabilityCanDecodeThumbnail ||
           WICBitmapDecoderCapabilityCanEnumerateMetadata ||
           WICBitmapDecoderCapabilitySameEncoder;
      }
      ....
    }

    V560 A part of conditional expression is always true. aitdecoder.cpp 634

    Diagnostics considered that part of the expression is always true and it is right, because the words WICBitmapDecoderCapabilityCanDecodeXXX are simply enum values named WICBitmapDecoderCapabilities :
    enum WICBitmapDecoderCapabilities
    {
      WICBitmapDecoderCapabilitySameEncoder = 0x1,
      WICBitmapDecoderCapabilityCanDecodeAllImages = 0x2,
      WICBitmapDecoderCapabilityCanDecodeSomeImages = 0x4,
      WICBitmapDecoderCapabilityCanEnumerateMetadata = 0x8,
      WICBitmapDecoderCapabilityCanDecodeThumbnail = 0x10,
      WICBITMAPDECODERCAPABILITIES_FORCE_DWORD = 0x7fffffff
    };

    As a result, it is likely that someone simply mixed up the characters and instead of the bitwise OR "|" wrote a logical OR "||". Unlike the C # compiler, C ++ easily swallowed it.

    3. Errors in initialization and in assigning variables.

    Of course, after refactoring often initialized variables remain twice in a row.
    STDMETHODIMP BaseFrameEncode::WritePixels(....)
    {
       result = S_OK;
       ....
       result = factory->CreateBitmapFromMemory(....);
    }

    V519 The 'result' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 269, 279. baseencoder.cpp 279

    When the variables are initialized through several lines of code, you can easily understand the person why he missed, but sometimes these two lines go in a row:
    STDMETHODIMP AitFrameEncode::Commit()
    {
       HRESULT result = E_UNEXPECTED;
       result = BaseFrameEncode::Commit();
       ....
    }

    V519 The 'result' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 320, 321. aitencoder.cpp 321

    Conclusion


    It is believed that C # code is less error prone than C ++; in certain cases, this is true. However, an interesting fact is that the bulk of the errors are not in specific constructions, but in simple terms. For example, in the conditions of an if statement . The static code analyzer for PVS-Studio C, C ++ and C # will allow you to control the quality of your code and will by all means protect you from fatal errors that may reach your user.


    If you want to share this article with an English-speaking audience, then please use the link to the translation: Alferov Vitalii. Source code of WPF samples by Microsoft was checked .

    Have you read the article and have a question?
    Often our articles are asked the same questions. We collected the answers here: Answers to questions from readers of articles about PVS-Studio, version 2015 . Please see the list.

    Also popular now: