Il codice sorgente degli esempi WPF di Microsoft è stato controllato

 C Programming >> Programmazione C >  >> Tags >> WPF
Il codice sorgente degli esempi WPF di Microsoft è stato controllato

Per far conoscere PVS-Studio, che ora è in grado di controllare non solo i progetti C++, ma anche C#, abbiamo deciso di controllare il codice sorgente degli esempi WPF, offerti da Microsoft.

Dopo il rilascio di Windows Vista, l'azienda ha introdotto un nuovo sottosistema per il rendering delle interfacce utente nelle applicazioni basate su Windows:Windows Presentation Foundation (WPF). Questo sottosistema grafico fa parte di .NET Framework, a partire dalla versione 3.0. Utilizza il linguaggio di markup XAML. Ora, ha quasi sostituito i WinForms obsoleti. A mio modesto parere, il principale svantaggio di WinForms era il fatto che eseguiva tutto il rendering sulla CPU. WPF si è avvicinato a questo in un modo più sensato e ha lasciato che DirectX eseguisse il rendering dei componenti. Ora WPF consente la realizzazione di interfacce universali per tre piattaforme contemporaneamente (PC, XBOXOne, Winphone) e ha praticamente estromesso WinForms.

Per eseguire l'analisi degli esempi WPF di Microsoft (il codice sorgente degli esempi), abbiamo utilizzato l'analizzatore di codice statico PVS-Studio, versione 6.05.

Una cosa interessante di questa soluzione è il fatto che insieme ai progetti scritti in C#, ci sono anche diversi progetti C++. Ma l'ho trovato solo dall'elenco dei bug trovati da PVS-Studio. Il plug-in PVS-Studio per Visual Studio, senza impostazioni aggiuntive da parte dell'utente, ha eseguito l'analisi e visualizzato avvisi sia per i progetti C++ che C#.

Figura 1. Come puoi vedere, nella finestra di PVS-Studio ci sono avvisi emessi sia per il codice C# che C++ (fare clic sull'immagine per ingrandirla).

Errori C#

1. Errori commessi durante la formazione delle condizioni dell'istruzione if

Per i programmatori è un problema comune:errori nei confronti. Diamo un'occhiata a loro.

In questo codice ci sono due condizioni assolutamente identiche:

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 È stato rilevato l'utilizzo del pattern 'if (A) {...} else if (A) {...}'. C'è una probabilità di presenza di un errore logico. Righe di controllo:418, 422. txtserializerwriter.cs 418

Non è molto chiaro cosa si intendesse qui, ma a quanto pare era qualcosa di diverso da quello che vediamo ora.

Ci piace fare le verifiche contro null nelle condizioni, e quindi cercare di proteggere il programma da eventi di emergenza. Possiamo anche dire che la maggior parte di se le condizioni sono null -verifica di alcuni campi o variabili. Ma a volte tali controlli possono essere ridondanti e contenere persino errori logici:

public static string FindNumeric(string content)
{
  string[] values = content.Split(' ');
  if (values != null)
  {
    return values[0];
  }
  return "none";
}

V3022 L'espressione 'valori !=null' è sempre vera. Util.cs 287

Possiamo presumere che l'autore volesse verificare che valori ha più di 0 elementi, personalmente non riuscivo a pensare a una situazione in cui Split restituisce un array vuoto. Ad ogni modo, la verifica contro null è completamente inutile qui.

Come ho già detto, il progetto contiene codice dalla diagnostica C++ e C#. Ho avuto l'impressione che il codice seguente sia stato scritto da un programmatore C++.

private void LoadNotes()
{
  var fs = new FileStream("NotesFile", FileMode.Open);
  if (fs != null)
  {
    ....
}

V3022 L'espressione 'fs !=null' è sempre vera. MainWindow.cs 66

In realtà, anche in C++ questa variante è errata, in C# sembrerà almeno "strana". Maggiori dettagli sul motivo per cui non è corretto scrivere tale codice sono forniti nell'articolo "Verifica di 7-Zip con l'analizzatore PVS-Studio" e continueremo a guardare il codice C#.

Non dobbiamo andare lontano per trovare altri frammenti di bug. C'erano due funzioni praticamente identiche nella soluzione (grazie a copia-incolla) con lo stesso errore:

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 L'espressione 'scrittore !=null' è sempre vera. htmlserializerwriter.cs 324

Scrittore non sarà un riferimento nullo...

Lanciare un errore in situazioni eccezionali non è la decisione peggiore. Ma la cosa principale è non commettere un errore nella condizione in cui l'eccezione dovrebbe essere generata, perché può creare un'impressione spiacevole agli occhi del nostro utente, quando il programma si arresta in modo anomalo all'improvviso.

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 Considerare di ispezionare 'sizeString.Length ==0 || sizeString.Length !=2' espressione. L'espressione è eccessiva o contiene un errore di stampa. MainWindow.cs 140

A giudicare dal testo dell'errore, il confronto con 0 è eccessivo, bastava verificare se sizeString.Length non è uguale a 2.

Nei lunghi corpi di se istruzioni a volte è molto difficile notare controlli privi di significato durante la revisione del codice.

private static void WriteElement(....)
{
  if (htmlWriter == null)
  {
    ....
  }
  else
  {
     if (htmlWriter != null && htmlElementName != null)
     {
       ....
  ....
}

V3063 Una parte dell'espressione condizionale è sempre vera:htmlWriter !=null. HtmlFromXamlConverter.cs 491

Nessun problema per l'analizzatore. A proposito, grazie al nostro amato copia-incolla, è stato riscontrato un errore in due progetti:HtmlToXamlDemo e Serializzazione dei documenti .

Ovviamente controlli privi di significato possono essere trovati non solo in funzioni lunghe, ma all'interno di più stringhe.

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 L'espressione 'clock.CurrentState !=ClockState.Active' è sempre vera. MainWindow.cs 103

In generale, va abbastanza bene, ma in seguito avremo un se istruzione annidata in un'altra istruzione if e un'altra... Se solo potessimo sbarazzarci di controlli senza significato per una migliore comprensione del codice, che viene letto più spesso di quanto non venga scritto...

Facciamo una breve pausa e diamo un'occhiata a una funzione che mi sono imbattuto di recente. Questo è il corpo della funzione:

private void UpdateSavings()
{
  Savings = TotalIncome - (Rent + Misc + Food);
  if (Savings < 0)
  {
  }
  else if (Savings >= 0)
  {
  }
}

L'espressione V3022 'Risparmio>=0' è sempre vera. NetIncome.cs 98

Inoltre abbiamo trovato molti (più di 60) confronti di numeri reali (doppio) con uno 0 preciso.

if (leftConst != null && leftConst.Value == 0)
{
  // 0 + y;  return y;
  return newRight;
}

Ad esempio:

  • V3024 Un confronto preciso dispari:leftConst.Value ==0. Considerare l'utilizzo di un confronto con precisione definita:Math.Abs(A - B)
  • V3024 Un confronto preciso dispari:leftConst.Value ==1. Considerare l'utilizzo di un confronto con precisione definita:Math.Abs(A - B)
  • V3024 Uno strano confronto preciso:leftConst.Value ==-1. Considerare l'utilizzo di un confronto con precisione definita:Math.Abs(A - B)
  • e così via...

Tutte le righe non rientreranno in un articolo. Questo avviso è per noi di terzo livello, perché la sua rilevanza dipende fortemente dalle specificità del programma. Nel caso ci siano valutazioni matematiche (manipolazioni con il valore), non vi è alcuna garanzia che otterremo un numero specifico:-1, 0, 1. Ma anche una leggera deviazione in 0.00000000001 porterà a risultati errati nei confronti. Ma se la logica del programma presuppone la scrittura di valori discreti sui numeri reali (doppio), allora questi controlli non sono un errore.

2. Errori nell'inizializzazione e nell'assegnazione delle variabili

Le funzioni sono ottime cose che aiutano non solo a rimuovere il codice duplicato, ma semplificano la leggibilità del codice in cui viene utilizzata questa funzione. È particolarmente importante che questa funzione svolga esattamente l'attività indicata nel suo nome e la firma della chiamata. Ma questo non è sempre il caso, ad esempio, si consideri il seguente frammento di codice. Scriverò l'intera funzione in modo che tu possa capire più chiaramente la situazione.

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 Il parametro 'fileName' viene sempre riscritto nel corpo del metodo prima di essere utilizzato. ThumbViewer.xaml.cs 192

Il nome del file che dovrebbe essere aperto, viene perso subito prima del suo primo utilizzo fileName =dialog.FileName . Sì, verrà aperta una finestra di dialogo e verrà scelto il file utente, ma perché abbiamo bisogno di un parametro che non è realmente utilizzato?

La mancanza di tempo e il copia-incolla a volte producono costruzioni molto strane:

public MailSettingsDialog()
{
  ....
  _timerClock = _timerClock = new DispatcherTimer(); 
  ....
}

V3005 La variabile '_timerClock' è assegnata a se stessa. MailSettingsDialog.cs 56

Questo potrebbe non sembrare l'errore di battitura più orribile, ma ci fa pensare "stiamo scrivendo nel posto giusto per la seconda volta?" Bene, per esempio, così:

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 La variabile 'clipboardInfo.Text' è assegnata a se stessa. MainWindow.cs 204

In generale, il codice abbonda di strani compiti:

private void DoParse(string commandLine)
{
  ....
  strLeft = strRight = string.Empty;
  strLeft = strs[0];
  strRight = strs[1];
  ....
}

V3008 Alla variabile 'strLeft' vengono assegnati valori due volte in successione. Forse questo è un errore. Righe di controllo:55, 54. CommandLine.cs 55

V3008 Alla variabile 'strRight' vengono assegnati valori due volte in successione. Forse questo è un errore. Righe di controllo:56, 54. CommandLine.cs 56

strLeft e strRight - sono solo variabili locali di tipo stringa.

Il codice seguente è ancora più errato. Per qualche motivo il programmatore ha fatto molte valutazioni e rivalutazioni e poi le ha scritte nella stessa variabile.

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 Alla variabile 'arg' vengono assegnati valori due volte di seguito. Forse questo è un errore. Righe di controllo:176, 173. CommandLine.cs 176

E alcuni altri esempi di incarichi primari privi di significato:

private void DrawFormattedText(DpiScale dpiInfo)
{
  ....
  Geometry geometry = new PathGeometry();
  geometry = formattedText.BuildGeometry(
     new System.Windows.Point(0, 0));
  ....
}
  • V3008 Alla variabile 't' vengono assegnati valori due volte in successione. Forse questo è un errore. Linee di controllo:141, 115. TrackBall.cs 141
  • V3008 Alla variabile 't' vengono assegnati valori due volte in successione. Forse questo è un errore. Linee di controllo:141, 115. TrackBall.cs 141
  • V3008 Alla variabile 'columnSpan' vengono assegnati valori due volte consecutive. Forse questo è un errore. Righe di controllo:2115, 2101. HtmlToXamlConverter.cs 2115
  • V3008 Alla variabile '_timerInterval' vengono assegnati valori due volte di seguito. Forse questo è un errore. Righe di controllo:52, 47. ClientForm.cs 52
  • V3008 Alla variabile 'matrix1' vengono assegnati valori due volte in successione. Forse questo è un errore. Righe di controllo:126, 125. MainWindow.cs 126
  • V3008 Alla variabile 'matrixResult' vengono assegnati valori due volte in successione. Forse questo è un errore. Righe di controllo:140, 138. MainWindow.cs 140
  • V3008 Alla variabile 'matrixResult' vengono assegnati valori due volte in successione. Forse questo è un errore. Linee di controllo:351, 349. MainWindow.cs 351
  • V3008 Alla variabile 'matrixResult' vengono assegnati valori due volte in successione. Forse questo è un errore. Righe di controllo:369, 367. MainWindow.cs 369
  • V3008 Alla variabile 'pointResult' vengono assegnati valori due volte consecutive. Forse questo è un errore. Linee di controllo:480, 478. MainWindow.cs 480
  • V3008 Alla variabile 'columnSpan' vengono assegnati valori due volte consecutive. Forse questo è un errore. Righe di controllo:1930, 1917. htmltoxamlconverter.cs 1930
  • V3008 Alla variabile 'geometry' vengono assegnati valori due volte in successione. Forse questo è un errore. Righe di controllo:56, 55. MainWindow.xaml.cs 56
  • V3008 Alla variabile 'pathGeometry' vengono assegnati valori due volte in successione. Forse questo è un errore. Righe di controllo:66, 65. MainWindow.xaml.cs 66

Non ha senso scrivere ogni esempio, ci sono bug più interessanti in attesa.

3. A coppia di varie errori

Lanciando l'eccezione, è importante salvare la chiamata allo stack, in modo da poter capire in seguito, guardando i log, "cosa è andato storto esattamente dal lato dell'utente", ma non tutti sanno come farlo.

public static object InvokePropertyOrMethod(....)
{
  try
  {
     ....
  }
  catch (MissingMethodException e)
  {
    ....
    throw e;
  }
  catch (AmbiguousMatchException e)
  {
     throw e;
  }
  return resultObject;
}

V3052 L'oggetto eccezione originale 'e' è stato ingoiato. Lo stack dell'eccezione originale potrebbe essere perso. ReflectionUtils.cs 797

V3052 L'oggetto eccezione originale 'e' è stato ingoiato. Lo stack dell'eccezione originale potrebbe essere perso. ReflectionUtils.cs 806

Secondo lo standard, se passiamo l'eccezione sopra nello stack delle chiamate di funzione per mezzo di throw e; , perderemo lo stack di chiamate che era prima della cattura dell'eccezione nel blocco catch. Per mantenere l'intera chiamata dello stack e la sua ulteriore continuazione, dobbiamo solo scrivere un tiro parola nel cattura blocco e basta.

A volte i controlli non sono necessari ea volte non sono sufficienti come nel codice seguente:

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 La funzione 'Sottostringa' potrebbe ricevere il valore '-1' mentre è previsto un valore non negativo. Esamina il secondo argomento. HtmlCSSParser.cs 645

Non è possibile verificare che fontFamily.Length è maggiore di 1, quindi sottraendo da fontFamily.Length numero 2 possiamo ottenere un valore inferiore a 0. E in questi casi questa funzione genera un'eccezione ArgumentOutOfRangeException .

Se sarebbe più sicuro scrivere un assegno:

if (fontFamilyList == null && fontFamily.Length > 1)

4. WPF bug

La DependencyProperty è una delle caratteristiche più notevoli di WPF. Creare proprietà in grado di notificare allo sviluppatore direttamente dalla casella le modifiche apportate è incredibilmente conveniente. Ma la cosa principale è evitare di confondere la firma per descriverli, è particolarmente importante ricordarlo quando si mostrano gli esempi, perché è ciò che le persone giudicano da

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:i nomi della proprietà registrata 'RadiusBase', e della proprietà 'Radius', non corrispondono tra loro. FireworkEffect.cs 196

In questo caso particolare, il nome registrato per una proprietà di dipendenza non corrisponde al nome della proprietà wrapper per accedere a DependencyProperty dal codice. Questa opzione causa grossi problemi quando si lavora dal markup XAML. WPF consente da XAML di accedere a una semplice proprietà Radius e leggere il valore da esso, ma le modifiche di questa proprietà non verranno recuperate da XAML.

In realtà, in PVS-Studio, ci sono una serie di diagnostica per rilevare errori nella firma durante la creazione di DependencyProperty [3044, 3045, 3046, 3047, 3048, 3049]. Ma la maggior parte degli errori di questo tipo porta all'arresto anomalo del programma non appena il programma inizia a utilizzare la classe con queste proprietà di dipendenza. Ecco perché questa diagnostica ha lo scopo di salvarci dalla ricerca e dall'analisi di lunghi testi di firme, soprattutto dopo la copia. Naturalmente, il modo più efficiente sarebbe controllare regolarmente il codice con PVS-Studio, non solo eseguire l'analisi della versione finale del programma.

Diamo un'occhiata a un altro avviso interessante. In questo caso si trattava del nostro nuovo diagnostico V3095. Questa diagnostica mostra i punti in cui accediamo prima alla variabile e quindi la verifichiamo rispetto a null.

private static XmlElement AddOrphanListItems(....)
{
  Debug.Assert(htmlLiElement.LocalName.ToLower() == "li");
  ....
  XmlNode htmlChildNode = htmlLiElement;
  var htmlChildNodeName = htmlChildNode == null 
      ? null 
      : htmlChildNode.LocalName.ToLower();
  ....
}

V3095 L'oggetto 'htmlLiElement' è stato utilizzato prima di essere verificato rispetto a null. Righe di controllo:916, 936. HtmlToXamlConverter.cs 916

In questo caso, nella condizione dell'operatore ternario controlliamo se la variabile htmlChildNode può essere nullo. Allo stesso tempo la variabile htmlChildNode, non è altro che un riferimento alla variabile htmlLiElement . Ma abbiamo avuto accesso alla variabile htmlLiElement senza l'accertamento contro nullo. Di conseguenza, abbiamo un codice che non verrà mai eseguito o otterremo un'eccezione NullReferenceException nella stringa htmlLiElement.LocalName.ToLower() .

Oltre agli errori che abbiamo descritto, viene prestata molta attenzione alla diagnostica V3072, che ha lo scopo di rilevare i campi con il tipo implementato da IDisposable interfaccia, ma la classe in cui i campi non sono dichiarati non ha questa implementazione.

internal class Email
{
  private readonly SmtpClient _client;
  ....
}

V3072 La classe 'Email' contenente i membri IDisposable non implementa IDisposable. Ispeziona:_client. E-mail.cs 15

IDusabile è sempre stato problematico. A volte Finalize può essere di grande aiuto, almeno nelle classi standard, per evitare errori critici legati al suo errato utilizzo. I programmatori spesso dimenticano, perdono o semplicemente non prestano attenzione al campo con il tipo, implementando questa interfaccia. Non è così facile giustificare tale codice o ammettere di avere un errore durante la revisione del codice, ma ci sono schemi a cui vale la pena prestare attenzione. In questa soluzione c'erano anche molti di questi avvisi:

  • V3072 La classe 'HtmlLexicalAnalyzer' contenente i membri IDisposable non implementa di per sé IDisposable. Esamina:_inputStringReader. HtmlLexicalAnalyzer.cs 16
  • V3072 La classe 'MainWindow' che contiene i membri IDisposable non implementa di per sé IDisposable. Esamina:_customersTableAdapter, _nwDataSet... MainWindow.cs 15
  • V3072 La classe 'MainWindow' che contiene i membri IDisposable non implementa di per sé IDisposable. Ispeziona:_listControl. MainWindow.cs 14
  • V3072 La classe 'ThumbViewer' contenente i membri IDisposable non implementa di per sé IDisposable. Esamina:_annStore, _annotationBuffer. ThumbViewer.xaml.cs 31
  • V3072 La classe 'HtmlLexicalAnalyzer' contenente i membri IDisposable non implementa di per sé IDisposable. Esamina:_inputStringReader. htmllexicalanalyzer.cs 24
  • V3072 La classe 'MainWindow' che contiene i membri IDisposable non implementa di per sé IDisposable. Ispezionare:_store. MainWindow.cs 20
  • V3072 La classe 'MainWindow' che contiene i membri IDisposable non implementa di per sé IDisposable. Ispeziona:_customCursor. MainWindow.cs 14
  • V3072 La classe 'MainWindow' che contiene i membri IDisposable non implementa di per sé IDisposable. Ispeziona:_synthesizer vocale. MainWindow.cs 14

Errori C++

1. Errori durante la scrittura delle condizioni dell'istruzione if

È stata una vera rivelazione per me trovare progetti C++ in questa soluzione, ma ciononostante anche questi sono bug, quindi diamo un'occhiata.

Come in C#, iniziamo con vari confronti. Diamo un'occhiata a quel bug di C++ che ho menzionato nel blocco C#.

STDMETHOD(CreateInstance)(....)
{
  ....
  T *obj = new T();
  if (NULL != obj)
  {
    ....
}

V668 Non ha senso testare il puntatore 'obj' rispetto a null, poiché la memoria è stata allocata utilizzando l'operatore 'new'. L'eccezione verrà generata in caso di errore di allocazione della memoria. classfactory.h 76

Se il nuovo l'operatore non è stato in grado di allocare la memoria, quindi secondo lo standard C++, un'eccezione std::bad_alloc() viene lanciato. Pertanto, la verifica contro null è priva di significato, come l'obj il puntatore non sarà mai uguale a NULL. Se è impossibile allocare la memoria, allora abbiamo un'eccezione che dovrebbe essere gestita a un livello superiore e la verifica contro null può essere semplicemente eliminata. Nel caso in cui non sia desiderabile avere eccezioni nell'applicazione, possiamo usare il nuovo operatore che non genera eccezioni (T *obj =new (std::nothrow) T() ), , e quindi il valore restituito può essere verificato rispetto a null. C'erano altri quattro controlli simili nella soluzione:

  • V668 Non ha senso testare il puntatore 'colors' rispetto a null, poiché la memoria è stata allocata utilizzando l'operatore 'new'. L'eccezione verrà generata in caso di errore di allocazione della memoria. aitdecoder.cpp 182
  • V668 Non ha senso testare il puntatore 'pixel' rispetto a null, poiché la memoria è stata allocata utilizzando l'operatore 'new'. L'eccezione verrà generata in caso di errore di allocazione della memoria. aitencoder.cpp 157
  • V668 Non ha senso testare il puntatore 'colors' rispetto a null, poiché la memoria è stata allocata utilizzando l'operatore 'new'. L'eccezione verrà generata in caso di errore di allocazione della memoria. aitencoder.cpp 221
  • V668 Non ha senso testare il puntatore 'bytes' rispetto a null, poiché la memoria è stata allocata utilizzando l'operatore 'new'. L'eccezione verrà generata in caso di errore di allocazione della memoria. aitencoder.cpp 275

Condizioni eccessive sono comuni per entrambi i linguaggi di programmazione:

if (bitmapLock && bitmap)
{
  if(bitmapLock)
  {
    bitmapLock->Release();
    bitmapLock = NULL;
  }
}

V571 Controllo ricorrente. La condizione 'bitmapLock' era già verificata nella riga 104. aitdecoder.cpp 106

Alcuni programmatori C# non sono consapevoli del fatto che le due operazioni seguenti sul tipo Nullable sono equivalenti:

  • _isInDesignMode !=null
  • _isInDesignMode.HasValue

Ecco perché scrivono i seguenti controlli:

if (_isInDesignMode != null && _isInDesignMode.HasValue)

Allo stesso tempo, alle persone C++ piace fare verifiche inutili contro null, prima di liberare la memoria allocata dall'indirizzo a cui punta.

static HRESULT OutputColorContext(....)
{
  ....
  if (pixels)
    delete[] pixels;
  ....
}

V809 Non è necessario verificare che un valore del puntatore non sia NULL. Il controllo "se (pixel)" può essere rimosso. aitencoder.cpp 189

static HRESULT OutputBitmapPalette(....)
{
  ....
  if (colors)
    delete[] colors;
  ....
}

V809 Non è necessario verificare che un valore del puntatore non sia NULL. Il controllo "se (colori)" può essere rimosso. aitencoder.cpp 241

static HRESULT OutputColorContext(....)
{
  if (bytes)
    delete[] bytes;
}

V809 Non è necessario verificare che un valore del puntatore non sia NULL. Il controllo 'if (byte)' può essere rimosso. aitencoder.cpp 292

2. Errore logico

Il codice seguente mostra una situazione piuttosto interessante di confronto logico, anche se non lo diresti.

STDMETHODIMP AitDecoder::QueryCapability(....)
{
  ....
  // If this is our format, we can do everything
  if (strcmp(bh.Name, "AIT") == 0)
  {
     *pCapability = 
       WICBitmapDecoderCapabilityCanDecodeAllImages ||
       WICBitmapDecoderCapabilityCanDecodeThumbnail ||
       WICBitmapDecoderCapabilityCanEnumerateMetadata ||
       WICBitmapDecoderCapabilitySameEncoder;
  }
  ....
}

V560 Una parte dell'espressione condizionale è sempre vera. aitdecoder.cpp 634

La diagnostica pensava che una parte della condizione fosse sempre vera ed è proprio giusta, come le parole WICBitmapDecoderCapabilityCanDecodeXXX sono solo enum valori con il nome WICBitmapDecoderCapabilities :

enum WICBitmapDecoderCapabilities
{
  WICBitmapDecoderCapabilitySameEncoder = 0x1,
  WICBitmapDecoderCapabilityCanDecodeAllImages = 0x2,
  WICBitmapDecoderCapabilityCanDecodeSomeImages = 0x4,
  WICBitmapDecoderCapabilityCanEnumerateMetadata = 0x8,
  WICBitmapDecoderCapabilityCanDecodeThumbnail = 0x10,
  WICBITMAPDECODERCAPABILITIES_FORCE_DWORD = 0x7fffffff
};

Di conseguenza, forse, qualcuno ha confuso i simboli e invece dell'OR bit per bit "|" ha scritto OR logico "||". A differenza del compilatore C#, quello C++ non ha riscontrato problemi.

3. Errore nell'inizializzazione e nell'assegnazione delle variabili

Ovviamente dopo il refactoring potremmo avere variabili che sono state inizializzate due volte di seguito.

STDMETHODIMP BaseFrameEncode::WritePixels(....)
{
   result = S_OK;
   ....
   result = factory->CreateBitmapFromMemory(....);
}

V519 Alla variabile 'risultato' vengono assegnati valori due volte in successione. Forse questo è un errore. Righe di controllo:269, 279. baseencoder.cpp 279

Quando le variabili vengono inizializzate ulteriormente dopo diverse righe di codice, possiamo facilmente capire perché la persona ha commesso un errore. A volte tali stringhe vengono scritte in successione:

STDMETHODIMP AitFrameEncode::Commit()
{
   HRESULT result = E_UNEXPECTED;
   result = BaseFrameEncode::Commit();
   ....
}

V519 Alla variabile 'risultato' vengono assegnati valori due volte in successione. Forse questo è un errore. Linee di controllo:320, 321. aitencoder.cpp 321

Conclusione

C'è un punto di vista, che C# è meno soggetto a errori rispetto a C++, e in alcuni casi è davvero così. Ma un fatto interessante è che la maggior parte degli errori non sono in costruzioni specifiche, ma in semplici espressioni. Ad esempio, nella condizione di se dichiarazione. L'analizzatore di codice statico PVS-Studio per C, C++ e C# ti consentirà di controllare la qualità del codice e farà del suo meglio per salvaguardarti dagli errori fatali che possono arrivare ai tuoi utenti.