Les rapports les plus rapides dans l'ouest sauvage. Et une poignée de bugs en plus ...

Image 3

Non seulement Microsoft a récemment publié le code open source de ses propres projets, mais d'autres sociétés suivent également cette tendance. Pour nous, les développeurs de PVS-Studio, c'est un excellent moyen de tester à nouveau l'analyseur, de voir les choses intéressantes qu'il peut trouver et d'en informer les auteurs du projet. Aujourd'hui, nous regardons à l'intérieur du projet Fast Reports.

Qu'est-ce qui a été vérifié?


FastReport est un générateur de rapports développé par Fast Reports . Écrit en C # et compatible avec .NET Standard 2.0+. Le code source du projet a été récemment publié sur GitHub , où il a été téléchargé pour une analyse plus approfondie.

Les rapports prennent en charge l'utilisation de texte, d'images, de lignes, de formes, de diagrammes, de tableaux, de codes à barres, etc. Ils peuvent être d'une seule page et de plusieurs pages, comprenant notamment, en plus des données, une couverture et une page arrière. Les sources de données peuvent être XML, CSV, Json, MS SQL, MySql, Oracle, Postgres, MongoDB, Couchbase, RavenDB, SQLite.

Il existe différentes façons de créer des modèles de rapport: à partir du code; en tant que fichier xml; en utilisant un concepteur en ligne ou FastReport Designer Community Edition.

Si nécessaire, les bibliothèques peuvent être téléchargées en tant que packages NuGet .

Vous pouvez en savoir plus sur les fonctionnalités du produit sur la page GitHub du projet .

Image 8


Il n'y a eu aucun problème avec l'assemblage du projet - je l'ai assemblé à partir de Visual Studio 2017, d'où il a ensuite été vérifié par le plugin PVS-Studio.

PVS-Studio est un analyseur statique qui recherche les erreurs dans le code C, C ++, C #, Java. Lors de l'analyse du code C #, vous pouvez utiliser l'analyseur Visual Studio IDE à l'aide du plug-in PVS-Studio, ou vous pouvez vérifier les projets à partir de la ligne de commande , pour laquelle l'utilitaire de ligne de commande PVS-Studio_Cmd.exe est utilisé. Si vous le souhaitez, vous pouvez configurer l'analyse sur le serveur de génération ou raccorder les résultats de l'analyse à SonarQube .

Voyons ce qui était intéressant cette fois.

Puisque le projet est petit, vous ne devez pas compter sur beaucoup de fautes de frappe et d'endroits suspects. Regardons ce qui a été trouvé et essayons même de reproduire quelque chose en pratique.

Lunettes et plus


Rencontré la méthode suivante:

public override string ToString() { if (_value == null) return null; return this.String; } 

Avertissement PVS-Studio : V3108 Il n'est pas recommandé de renvoyer 'null' à partir de la méthode 'ToSting ()'. Variant.cs 1519

Oui, renvoyer null à partir d'une méthode ToString () substituée n'est pas une erreur en soi, mais c'est quand même un mauvais style. Cela est indiqué, entre autres, dans la documentation de Microsoft : Votre remplacement ToString () ne doit pas retourner Empty ou une chaîne nulle . Les développeurs qui n'attendent pas de retour nul comme valeur de retour de ToString () peuvent être désagréablement surpris lorsqu'une ArgumentNullException est levée lors de l'exécution du code ci-dessous (à condition que la méthode d'extension soit appelée pour IEnumerable <T> ).

 Variant varObj = new Variant(); varObj.ToString().Contains(character); 

Vous pouvez trouver à redire au fait que l'exemple est synthétique, mais l'essence de cela ne change pas.

De plus, ce code est commenté comme suit:

 /// <summary> /// Returns <see cref="String"/> property unless the value on the right /// is null. If the value on the right is null, returns "". /// </summary> /// <returns></returns> 

Oups Au lieu de "" retourne null .

Continuons.

Il existe une classe FastString dans la bibliothèque . Description: Alternative rapide de StringBuilder . En fait, cette classe contient un champ de type StringBuilder . Les constructeurs de la classe FastString appellent la méthode Init , qui initialise le champ correspondant.

Le code de l'un des constructeurs:

 public FastString() { Init(initCapacity); } 

Et le code de la méthode Init :

 private void Init(int iniCapacity) { sb = new StringBuilder(iniCapacity); //chars = new char[iniCapacity]; //capacity = iniCapacity; } 

Si vous le souhaitez, vous pouvez accéder au champ sb via la propriété StringBuilder :

 public StringBuilder StringBuilder { get { return sb; } } 

Au total, FastString a 3 constructeurs:

 public FastString(); public FastString(int iniCapacity); public FastString(string initValue); 

J'ai déjà montré au corps du premier designer qu'ils font les deux suppositions restantes, je pense, n'est pas non plus difficile. Et maintenant, attention. Devinez ce que le code suivant produira:

 FastString fs = new FastString(256); Console.WriteLine(fs.StringBuilder.Capacity); 

Attention, la réponse:

Image 2


De façon inattendue? Regardons le corps du constructeur correspondant:

 public FastString(int iniCapacity) { Init(initCapacity); } 

Les lecteurs réguliers de nos articles devraient déjà avoir un œil pour trouver un problème ici. L'analyseur oculaire (parfum, logique, appelez-le comme vous voulez) est définitivement engourdi, et il a trouvé un problème: le paramètre de constructeur V3117 'iniCapacity' n'est pas utilisé. FastString.cs 434

Quelle coïncidence si le code de classe contient un champ constant initCapacity , qui est passé en argument à la méthode Init au lieu du paramètre constructeur iniCapacity ...

 private const int initCapacity = 32; 

Lorsque vous utilisez des noms similaires, vous devez être très, très prudent. Partout où des erreurs ont été commises concernant l'utilisation de noms similaires - projets en C, C ++, C #, Java - il y avait des fautes de ce type partout ...

Soit dit en passant, sur les fautes de frappe.

Faisons l'exemple simple suivant et voyons comment cela fonctionne:

 static void Main(string[] args) { TextObject textObj = new TextObject(); textObj.ParagraphFormat = null; Console.WriteLine("Ok"); } 

Comme vous l'avez peut-être deviné, la sortie sera différente de la chaîne «Ok» :)

Lequel? Ainsi, par exemple:

Image 1


Le problème réside dans la propriété ParagraphFormat et dans l'utilisation de noms similaires:

 public ParagraphFormat ParagraphFormat { get { return paragraphFormat; } set { ParagraphFormat = value; } } 

PVS-Studio Warning : V3110 Possible récursion infinie à l'intérieur de la propriété 'ParagraphFormat'. TextObject.cs 281

La propriété ParagraphFormat est un wrapper sur le champ paragraphFormat . De plus, son accesseur de propriété get est orthographié correctement, mais l'accesseur de propriété set contient une faute de frappe: au lieu du champ, l'enregistrement se produit dans la même propriété, ce qui provoque une récursivité. Encore une fois, une erreur liée à des noms similaires.

Considérez l'extrait de code suivant.

 public override Run Split(float availableWidth, out Run secondPart) { .... if (r.Width > availableWidth) { List<CharWithIndex> list = new List<CharWithIndex>(); for (int i = point; i < size; i++) list.Add(chars[i]); secondPart = new RunText(renderer, word, style, list, left + r.Width, charIndex); list.Clear(); for (int i = 0; i < point; i++) list.Add(chars[i]); r = new RunText(renderer, word, style, list, left, charIndex); return r; } else { List<CharWithIndex> list = new List<CharWithIndex>(); for (int i = point; i < size; i++) list.Add(chars[i]); secondPart = new RunText(renderer, word, style, list, left + r.Width, charIndex); list.Clear(); for (int i = 0; i < point; i++) list.Add(chars[i]); r = new RunText(renderer, word, style, list, left, charIndex); return r; } .... } 

PVS-Studio Warning : V3004 L' instruction 'then' est équivalente à l'instruction 'else'. HtmlTextRenderer.cs 2092

Un petit copier-coller, et maintenant, quelle que soit la valeur de l'expression r.Width> availableWidth , les mêmes actions seront effectuées. Supprimez l' instruction if ou modifiez la logique dans l'une des branches.

 public static string GetExpression(FindTextArgs args, bool skipStrings) { while (args.StartIndex < args.Text.Length) { if (!FindMatchingBrackets(args, skipStrings)) break; return args.FoundText; } return ""; } 

Avertissement de l'analyseur : V3020 Un «retour» inconditionnel dans une boucle. CodeUtils.cs 262

En raison de l' instruction de retour inconditionnelle , pas plus d'une itération sera exécutée pour la boucle ci-dessus. Peut-être que ce code est sorti après refactoring, ou peut-être que c'est juste une façon inhabituelle de faire ce qui pourrait être fait sans boucle.

 private int FindBarItem(string c) { for (int i = 0; i < tabelle_cb.Length; i++) { if (c == tabelle_cb[i].c) return i; } return -1; } internal override string GetPattern() { string result = tabelle_cb[FindBarItem("A")].data + "0"; foreach (char c in text) { int idx = FindBarItem(c.ToString()); result += tabelle_cb[idx].data + "0"; } result += tabelle_cb[FindBarItem("B")].data; return result; } 

Avertissement PVS-Studio : V3106 Valeur d'index négative possible. La valeur de l'indice 'idx' pourrait atteindre -1. BarcodeCodabar.cs 70

Code potentiellement dangereux. La méthode FindBarItem peut renvoyer -1 si elle ne trouve pas l'élément passé en tant que paramètre. Dans le code appelant (méthode GetPattern ), cette valeur est écrite dans la variable idx et est utilisée comme index du tableau tabelle_cb sans vérification préalable. Lors de l'accès à l'index -1, une exception de type IndexOutOfRangeException sera levée .

Continuons.

 protected override void Finish() { .... if (saveStreams) { FinishSaveStreams(); } else { if (singlePage) { if (saveStreams) { int fileIndex = GeneratedFiles.IndexOf(singlePageFileName); DoPageEnd(generatedStreams[fileIndex]); } else { .... } .... } .... } .... } 

PVS-Studio Warning : V3022 Expression 'saveStreams' est toujours faux. HTMLExport.cs 849

Le code ci-dessus avec l'obtention de la valeur fileIndex et l'appel de la méthode DoPageEnd ne sera jamais exécuté, car le résultat de la deuxième expression saveStreams dans le code sera toujours faux .

Des plus intéressants, c'est peut-être tout (vous ne vous attendiez pas à un article dans l'esprit de l'analyse Mono ?). Il y avait d'autres avertissements de l'analyseur, mais ils ne me semblaient pas assez intéressants pour les inclure dans l'article (une partie reste toujours en coulisses).

Connaître la conception serait utile pour leur analyse, donc idéalement, les auteurs devraient examiner ces avertissements par eux-mêmes. Ce sont des avertissements tels que V3083 (un appel potentiellement dangereux aux gestionnaires d'événements), V3022 (la condition est toujours vraie / fausse (dans ce cas, souvent en raison de méthodes qui renvoient une seule valeur)), V3072 , V3073 (travaillant avec IDisposable ) et autres.

Si tout cela n'est pas pertinent, vous pouvez:


Conclusion


Image 4


Malgré le fait que l'article était court, j'ai été ravi de «toucher» les avertissements de l'analyseur avec mes mains - pour voir comment ce que jure l'analyseur se manifeste dans la pratique.

Je souhaite aux auteurs du projet de réussir, de corriger les problèmes découverts, et je veux saluer un pas vers la communauté open-source!

Je conseille aux autres d'essayer l'analyseur sur votre code et de voir ce que vous pouvez trouver intéressant.

Meilleurs vœux!



Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Sergey Vasiliev. Les rapports les plus rapides du Far West - et une poignée de bugs ...

Source: https://habr.com/ru/post/fr431370/


All Articles