PVS-Studio passe aux nuages: Azure DevOps

Image 9

Il s'agit du deuxième article sur l'utilisation de l'analyseur statique PVS-Studio dans les systèmes CI cloud, et cette fois, nous examinerons la plate-forme Azure DevOps - une solution cloud CI \ CD de Microsoft. En tant que projet analysé cette fois, considérez ShareX.

Nous aurons besoin de trois composants. Le premier est l'analyseur statique PVS-Studio. Le second est Azure DevOps, avec lequel nous intégrerons l'analyseur. Le troisième est un projet que nous allons vérifier pour démontrer les capacités de PVS-Studio lorsque vous travaillez dans le cloud. Commençons donc.

PVS-Studio est un analyseur de code statique pour la recherche d'erreurs et de défauts de sécurité. Effectue une analyse de code en C, C ++, C # et Java.

Azure DevOps . La plate-forme Azure DevOps comprend des outils tels que Azure Pipeline, Azure Board, Azure Artifacts et autres pour accélérer le processus de création de logiciels et améliorer sa qualité.

ShareX est une application gratuite qui vous permet de capturer et d'enregistrer n'importe quelle partie de l'écran. Le projet est écrit en C # et est idéal pour montrer comment exécuter l'analyseur statique. Le code source du projet est disponible sur GitHub .

La sortie de la commande cloc pour le projet ShareX:
La langue
les fichiers
vierge
commenter
Code
C #
696
20658
24423
102565
Script MSBuild
11
1
77
5859
En d'autres termes, le projet est petit, mais tout à fait suffisant pour démontrer le travail de PVS-Studio en conjonction avec une plateforme cloud.

Mettons en place


Pour commencer dans Azure DevOps, cliquez sur le lien et cliquez sur le bouton «Commencer gratuitement avec GitHub».

Image 2

Donner à l'application Microsoft l'accès aux données du compte GitHub.

Image 1

Pour terminer l'inscription, vous devrez créer un compte Microsoft.

Image 12

Après l'enregistrement, créez un projet:

Image 5

Ensuite, nous devons aller dans la section «Pipelines» - «Builds» et créer un nouveau pipeline de construction

Image 8

À la question où se trouve notre code, nous répondrons - GitHub.

Image 13

Nous autorisons l'application Azure Pipelines et sélectionnons le référentiel avec le projet pour lequel nous allons configurer le lancement de l'analyseur statique

Image 7

Dans la fenêtre de sélection du modèle, sélectionnez «Starter pipeline».

Image 17

Nous pouvons exécuter une analyse statique du code de projet de deux manières: en utilisant des agents hébergés par Microsoft ou auto-hébergés.

Dans la première version, nous utiliserons des agents hébergés par Microsoft. Ces agents sont des machines virtuelles ordinaires qui démarrent lorsque nous démarrons notre pipeline et sont supprimées après la fin de la tâche. L'utilisation de tels agents vous permet de ne pas perdre de temps à les prendre en charge et à les mettre à jour, mais impose certaines restrictions, par exemple l'impossibilité d'installer des logiciels supplémentaires utilisés pour construire le projet.

Remplacez notre configuration par défaut par ce qui suit pour utiliser des agents hébergés par Microsoft:

#    #      master- trigger: - master #         # ,   Docker-, #      Windows Server 1803 pool: vmImage: 'win1803' container: microsoft/dotnet-framework:4.7.2-sdk-windowsservercore-1803 steps: #    - task: PowerShell@2 inputs: targetType: 'inline' script: 'Invoke-WebRequest -Uri https://files.viva64.com/PVS-Studio_setup.exe -OutFile PVS-Studio_setup.exe' - task: CmdLine@2 inputs: workingDirectory: $(System.DefaultWorkingDirectory) script: | #      nuget restore .\ShareX.sln #  ,        md .\PVSTestResults #   PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /COMPONENTS=Core #        "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" credentials -u $(PVS_USERNAME) -n $(PVS_KEY) #        html. "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" -t .\ShareX.sln -o .\PVSTestResults\ShareX.plog "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe" -t html -o .\PVSTestResults\ .\PVSTestResults\ShareX.plog #    - task: PublishBuildArtifacts@1 inputs: pathToPublish: PVSTestResults artifactName: PVSTestResults 

Remarque: selon la documentation , le conteneur utilisé doit être mis en cache dans l'image de la machine virtuelle, mais au moment de la rédaction de cet article ne fonctionne pas et le conteneur est téléchargé à chaque démarrage de la tâche, ce qui affecte négativement le temps d'exécution.

Enregistrez le pipeline et créez les variables qui seront utilisées pour créer le fichier de licence. Pour ce faire, ouvrez la fenêtre d'édition du pipeline et dans le coin supérieur droit cliquez sur le bouton «Variables».

Image 14

Ajoutez deux variables - PVS_USERNAME et PVS_KEY , contenant respectivement le nom d'utilisateur et la clé de licence. Lors de la création de la variable PVS_KEY , n'oubliez pas de cocher l'élément "Garder cette valeur secrète" pour crypter la valeur de la variable avec une clé RSA 2048 bits, ainsi que pour supprimer la sortie de la valeur de la variable dans le journal d'exécution de la tâche.

Image 15

Nous enregistrons les variables et démarrons le pipeline avec le bouton «Exécuter».

La deuxième option pour exécuter l'analyse consiste à utiliser un agent auto-hébergé. Les agents auto-hébergés sont des agents que nous configurons et gérons nous-mêmes. Ces agents offrent plus de possibilités d'installation de logiciels, ce qui est nécessaire pour l'assemblage et les tests de notre produit logiciel.

Avant d'utiliser de tels agents, ils doivent être configurés conformément aux instructions et un analyseur statique doit être installé et configuré .

Pour démarrer la tâche sur un agent auto-hébergé, nous remplaçons la configuration par défaut proposée par la suivante:

 #    #    master- trigger: - master #    self-hosted    'MyPool' pool: 'MyPool' steps: - task: CmdLine@2 inputs: workingDirectory: $(System.DefaultWorkingDirectory) script: | #      nuget restore .\ShareX.sln #  ,        md .\PVSTestResults #        html. "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" -t .\ShareX.sln -o .\PVSTestResults\ShareX.plog "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe" -t html -o .\PVSTestResults\ .\PVSTestResults\ShareX.plog #    - task: PublishBuildArtifacts@1 inputs: pathToPublish: PVSTestResults artifactName: PVSTestResults 

Après avoir terminé la tâche, l'archive avec les rapports de l'analyseur peut être téléchargée sur l'onglet Résumé, ou nous pouvons utiliser l'extension Envoyer un courrier électronique , qui vous permet de configurer l'envoi d'e-mails, ou chercher un outil plus pratique pour nous sur la place de marché .

Image 21

À propos des résultats d'analyse


Examinons maintenant certaines des erreurs détectées dans le projet vérifié - ShareX.

Contrôles redondants

Pour s'échauffer, commençons par de simples failles dans le code, à savoir avec des contrôles redondants:

 private void PbThumbnail_MouseMove(object sender, MouseEventArgs e) { .... IDataObject dataObject = new DataObject(DataFormats.FileDrop, new string[] { Task.Info.FilePath }); if (dataObject != null) { Program.MainForm.AllowDrop = false; dragBoxFromMouseDown = Rectangle.Empty; pbThumbnail.DoDragDrop(dataObject, DragDropEffects.Copy | DragDropEffects.Move); Program.MainForm.AllowDrop = true; } .... } 

Avertissement PVS-Studio : V3022 [CWE-571] L'expression 'dataObject! = Null' est toujours vraie. TaskThumbnailPanel.cs 415

Faites attention à vérifier la variable dataObject pour null . Pourquoi est-elle ici? dataObject ne peut tout simplement pas être null dans ce cas, car il est initialisé avec une référence à l'objet créé. En conséquence, nous avons une vérification redondante. Est-ce critique? Non. Semble concis? Non. Cette vérification est clairement mieux supprimée afin de ne pas encombrer le code.

Jetons un coup d'œil à un autre morceau de code, auquel vous pouvez faire des commentaires similaires:

 private static Image GetDIBImage(MemoryStream ms) { .... try { .... return new Bitmap(bmp); .... } finally { if (gcHandle != IntPtr.Zero) { GCHandle.FromIntPtr(gcHandle).Free(); } } .... } private static Image GetImageAlternative() { .... using (MemoryStream ms = dataObject.GetData(format) as MemoryStream) { if (ms != null) { try { Image img = GetDIBImage(ms); if (img != null) { return img; } } catch (Exception e) { DebugHelper.WriteException(e); } } } .... } 

Avertissement PVS-Studio : V3022 [CWE-571] L'expression 'img! = Null' est toujours vraie. ClipboardHelpers.cs 289

La méthode GetImageAlternative vérifie à nouveau que la variable img n'est pas nulle immédiatement après la création d'une nouvelle instance de la classe Bitmap . La différence avec l'exemple précédent ici est que pour initialiser la variable img , nous n'utilisons pas le constructeur, mais la méthode GetDIBImage . L'auteur du code suppose qu'une exception peut se produire dans cette méthode, mais déclare uniquement try et finalement bloque, en omettant catch . Par conséquent, si une exception se produit, la méthode appelante - GetImageAlternative - ne recevra pas de référence à un objet de type Bitmap, mais sera forcée de gérer l'exception dans son propre bloc catch . Dans ce cas, la variable img ne sera pas initialisée, et le thread d'exécution n'atteindra même pas le contrôle img! = Null , mais tombera immédiatement dans le bloc catch . Par conséquent, l'analyseur a indiqué une validation redondante.

Considérez l'exemple d'avertissement suivant avec le code V3022 :

 private void btnCopyLink_Click(object sender, EventArgs e) { .... if (lvClipboardFormats.SelectedItems.Count == 0) { url = lvClipboardFormats.Items[0].SubItems[1].Text; } else if (lvClipboardFormats.SelectedItems.Count > 0) { url = lvClipboardFormats.SelectedItems[0].SubItems[1].Text; } .... } 

Avertissement PVS-Studio : V3022 [CWE-571] L'expression 'lvClipboardFormats.SelectedItems.Count> 0' est toujours vraie. AfterUploadForm.cs 155

Regardons la deuxième expression conditionnelle. Là, nous vérifions la valeur de la propriété Count en lecture seule. Cette propriété affiche le nombre d'éléments dans une instance de la collection SelectedItems . La condition n'est satisfaite que si la propriété Count est supérieure à zéro. Tout irait bien, mais ce n'est que dans l' instruction if externe que le compte est déjà vérifié. Une instance de la collection SelectedItems ne peut pas avoir le nombre d'éléments inférieur à zéro, par conséquent, Count prend une valeur égale à zéro ou supérieure à zéro. Étant donné que nous avons déjà effectué une vérification dans la première instruction if que Count est zéro et qu'il s'est avéré être faux, cela n'a aucun sens d'écrire une autre vérification sur la branche else que Count est supérieur à zéro.

Le dernier exemple d'erreur numéro V3022 est le fragment de code suivant:

 private void DrawCursorGraphics(Graphics g) { .... int cursorOffsetX = 10, cursorOffsetY = 10, itemGap = 10, itemCount = 0; Size totalSize = Size.Empty; int magnifierPosition = 0; Bitmap magnifier = null; if (Options.ShowMagnifier) { if (itemCount > 0) totalSize.Height += itemGap; .... } .... } 

PVS-Studio Warning : V3022 Expression 'itemCount> 0' est toujours false. RegionCaptureForm.cs 1100.

L'analyseur a remarqué que la condition itemCount> 0 sera toujours fausse, car une déclaration un peu plus élevée est effectuée et la variable itemCount est définie sur zéro en même temps. Jusqu'à la condition même, cette variable n'est utilisée nulle part et ne change pas, par conséquent, l'analyseur a tiré la bonne conclusion sur l'expression conditionnelle, dont la valeur est toujours fausse.

Eh bien, regardons maintenant quelque chose de vraiment intéressant.

La meilleure façon de comprendre le bogue est de visualiser le bogue.

Il nous semble qu'une erreur assez intéressante a été trouvée à cet endroit:

 public static void Pixelate(Bitmap bmp, int pixelSize) { .... float r = 0, g = 0, b = 0, a = 0; float weightedCount = 0; for (int y2 = y; y2 < yLimit; y2++) { for (int x2 = x; x2 < xLimit; x2++) { ColorBgra color = unsafeBitmap.GetPixel(x2, y2); float pixelWeight = color.Alpha / 255; r += color.Red * pixelWeight; g += color.Green * pixelWeight; b += color.Blue * pixelWeight; a += color.Alpha * pixelWeight; weightedCount += pixelWeight; } } .... ColorBgra averageColor = new ColorBgra((byte)(b / weightedCount), (byte)(g / weightedCount), (byte)(r / weightedCount), (byte)(a / pixelCount)); .... } 

Je ne veux pas révéler immédiatement toutes les cartes et montrer ce que notre analyseur a trouvé ici, alors reportons ce moment pour un court instant.

Par le nom de la méthode, il est facile de deviner ce qu'elle fait - vous lui soumettez une image ou un fragment de l'image en entrée, et elle effectue sa pixellisation. Le code de la méthode est assez long, donc nous ne le donnerons pas ici dans son intégralité, mais essayez simplement d'expliquer son algorithme et d'expliquer quel type de bogue PVS-Studio a trouvé ici.

Cette méthode accepte deux paramètres en entrée: un objet de type Bitmap et une valeur de type int , qui indique la taille des pixels. L'algorithme de fonctionnement est assez simple:

1) On décompose le fragment de l'image reçue à l'entrée en carrés de côté égal à la taille de la pixellisation. Par exemple, si nous avons une taille de pixelisation de 15, nous obtenons alors un carré contenant 15x15 = 225 pixels.

2) Ensuite, nous contournons chaque pixel de ce carré et accumulons les valeurs des champs Rouge , Vert , Bleu et Alpha en variables intermédiaires, et en multipliant précédemment la valeur de couleur correspondante et la valeur de canal alpha par la variable pixelWeight , obtenue en divisant la valeur Alpha par 255 (la variable Alpha a type octet ). De plus, lors de la traversée de pixels, nous additionnons les valeurs enregistrées dans pixelWeight dans une variable appelée weightedCount .

L'extrait de code qui effectue les étapes ci-dessus est le suivant:

 ColorBgra color = unsafeBitmap.GetPixel(x2, y2); float pixelWeight = color.Alpha / 255; r += color.Red * pixelWeight; g += color.Green * pixelWeight; b += color.Blue * pixelWeight; a += color.Alpha * pixelWeight; weightedCount += pixelWeight; 

Par ailleurs, notez que si la valeur de la variable Alpha est zéro, pixelWeight n'ajoutera aucune valeur à la variable weightedCount pour ce pixel. Nous en aurons besoin à l'avenir.

3) Après avoir contourné tous les pixels du carré actuel, nous pouvons composer la couleur «moyenne» générale pour ce carré. Le code qui effectue ces actions est le suivant:

 ColorBgra averageColor = new ColorBgra((byte)(b / weightedCount), (byte)(g / weightedCount), (byte)(r / weightedCount), (byte)(a / pixelCount)); 

4) Maintenant que nous avons la couleur finale et l' écrivons dans la variable averageColor , nous pouvons à nouveau faire le tour de chaque pixel du carré et lui affecter une valeur de averageColor .

5) Nous revenons à l'étape 2 tant qu'il reste des carrés bruts.

Encore une fois, nous notons que la variable weightedCount n'est pas égale au nombre de tous les pixels au carré. Par exemple, si un pixel absolument transparent est rencontré dans l'image (la valeur est nulle sur le canal alpha), alors la variable pixelWeight sera nulle pour ce pixel ( 0/255 = 0), par conséquent, ce pixel ne contribuera pas à la formation de la valeur de la variable weightedCount . C'est logique - cela n'a aucun sens de prendre en compte les couleurs d'un pixel absolument transparent.

Tout semble assez raisonnable - la pixellisation devrait fonctionner correctement. Et cela fonctionne vraiment bien. Ce n'est tout simplement pas pour les images png qui ont des pixels avec des valeurs dans le canal alpha inférieures à 255 et différentes de zéro. Faites attention à l'image pixélisée ci-dessous:

Image 3


Avez-vous vu la pixellisation? Et nous ne le sommes pas. Eh bien, maintenant, révélons cette petite intrigue et expliquons où exactement le bogue est caché dans cette méthode. L'erreur s'est glissée dans la ligne de calcul de la valeur de la variable pixelWeight :

 float pixelWeight = color.Alpha / 255; 

Le fait est que l'auteur du code, déclarant la variable pixelWeight comme float , a impliqué que lors de la division du champ Alpha par 255, en plus de zéro et un, des nombres fractionnaires devraient être obtenus. C'est là que réside le problème, puisque la variable Alpha est de type octet , et lorsque nous la divisons par 255, nous obtenons une valeur entière, et alors seulement elle sera implicitement transtypée pour flotter , par conséquent, la partie fractionnaire est perdue.

L'incapacité à pixelliser des images PNG présentant un certain degré de transparence est facile à expliquer. Étant donné que les valeurs du canal alpha pour ces pixels se situent dans la plage 0 <Alpha <255, lors de la division de la variable Alpha par 255, nous obtiendrons toujours 0. Par conséquent, les valeurs des variables pixelWeight , r , g , b , a , weightedCount sont également toujours sera nul. Par conséquent, notre couleur moyenne averageColor sera avec des valeurs nulles sur tous les canaux: rouge - 0, bleu - 0, vert - 0, alpha - 0. En remplissant le carré avec cette couleur, nous ne changeons pas la couleur d'origine des pixels, car averageColor est absolument transparent . Pour corriger cette erreur, il vous suffit de convertir explicitement le champ Alpha en type flottant . La ligne de code corrigée peut ressembler à ceci:

 float pixelWeight = (float)color.Alpha / 255; 

Et il est temps de citer le message que PVS-Studio a donné au code incorrect:

Avertissement PVS-Studio : V3041 [CWE-682] L'expression a été implicitement convertie du type 'int' en type 'float'. Pensez à utiliser un transtypage de type explicite pour éviter la perte d'une partie fractionnaire. Un exemple: double A = (double) (X) / Y;. ImageHelpers.cs 1119.

Et à titre de comparaison, nous donnons une capture d'écran d'une image vraiment pixélisée obtenue sur une version fixe de l'application:

Image 6

Potentiel NullReferenceException

 public static bool AddMetadata(Image img, int id, string text) { .... pi.Value = bytesText; if (pi != null) { img.SetPropertyItem(pi); return true; } .... } 

Avertissement PVS-Studio: V3095 [CWE-476] L'objet 'pi' a été utilisé avant d'être vérifié par rapport à null. Vérifiez les lignes: 801, 803. ImageHelpers.cs 801

Ce fragment de code montre que son auteur s'attendait à ce que la variable pi soit nulle , c'est pourquoi la vérification pi! = Null est effectuée avant d'appeler la méthode SetPropertyItem . Il est étrange qu'avant cette vérification, un tableau d'octets soit affecté à la propriété pi.Value , car si pi est nul , une exception de type NullReferenceException sera levée.

Une situation similaire a été observée ailleurs:

 private static void Task_TaskCompleted(WorkerTask task) { .... task.KeepImage = false; if (task != null) { if (task.RequestSettingUpdate) { Program.MainForm.UpdateCheckStates(); } .... } .... } 

Avertissement PVS-Studio: V3095 [CWE-476] L'objet 'tâche' a été utilisé avant d'être vérifié par rapport à null. Vérifiez les lignes: 268, 270. TaskManager.cs 268

PVS-Studio a trouvé une autre erreur similaire. La signification est toujours la même, il n'y a donc pas grand besoin de donner un fragment de code, nous nous limitons au message de l'analyseur.

Avertissement PVS-Studio: V3095 [CWE-476] L'objet 'Config.PhotobucketAccountInfo' a été utilisé avant d'être vérifié par rapport à null. Vérifier les lignes: 216, 219. UploadersConfigForm.cs 216

La même valeur de retour

Un morceau de code suspect a été découvert dans la méthode EvalWindows de la classe WindowsList , qui retourne true en toutes circonstances:

 public class WindowsList { public List<IntPtr> IgnoreWindows { get; set; } .... public WindowsList() { IgnoreWindows = new List<IntPtr>(); } public WindowsList(IntPtr ignoreWindow) : this() { IgnoreWindows.Add(ignoreWindow); } .... private bool EvalWindows(IntPtr hWnd, IntPtr lParam) { if (IgnoreWindows.Any(window => hWnd == window)) { return true; // <= } windows.Add(new WindowInfo(hWnd)); return true; // <= } } 

Avertissement PVS-Studio: V3009 Il est étrange que cette méthode renvoie toujours une seule et même valeur de «vrai». WindowsList.cs 82

Il semble logique que si un pointeur avec la même valeur que hWnd était trouvé dans la liste avec le nom IgnoreWindows , alors la méthode devrait retourner false .

La liste IgnoreWindows peut être remplie soit en appelant le constructeur WindowsList (IntPtr ignoreWindow) , soit directement via l'accès à la propriété, car elle est publique. D'une manière ou d'une autre, selon Visual Studio, pour le moment, dans le code, cette liste n'est en aucun cas remplie. C'est un autre endroit étrange de cette méthode.

Appel non sécurisé aux gestionnaires d'événements

 protected void OnNewsLoaded() { if (NewsLoaded != null) { NewsLoaded(this, EventArgs.Empty); } } 

Avertissement PVS-Studio: V3083 [CWE-367] L'appel non sécurisé de l'événement 'NewsLoaded', NullReferenceException est possible. Pensez à affecter un événement à une variable locale avant de l'invoquer. NewsListControl.cs 111

Dans ce cas, la situation désagréable suivante peut se produire: après avoir vérifié la variable NewsLoaded pour une inégalité nulle , la méthode qui traite l'événement peut être désabonnée, par exemple, dans un autre thread, et lorsque nous entrons dans le corps de l' instruction conditionnelle if , la variable NewsLoaded sera déjà est nul . Tenter d'appeler des abonnés sur un événement NewsLoaded qui est null lèvera une exception NullReferenceException . Il est beaucoup plus sûr d'utiliser l'opérateur conditionnel nul et de réécrire le code ci-dessus comme suit:

 protected void OnNewsLoaded() { NewsLoaded?.Invoke(this, EventArgs.Empty); } 

L'analyseur a indiqué 68 endroits plus similaires. Nous ne les décrirons pas ici - le modèle de l'appel d'événement en eux est similaire.

Retourne null de ToString

Il n'y a pas si longtemps, à partir d'un article intéressant d'un collègue , j'ai découvert que Microsoft ne recommandait pas de retourner null à partir d'une méthode ToString remplacée. PVS-Studio en est bien conscient:

 public override string ToString() { lock (loggerLock) { if (sbMessages != null && sbMessages.Length > 0) { return sbMessages.ToString(); } return null; } } 

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

Pourquoi convient-il si je n'utilise pas?

 public SeafileCheckAccInfoResponse GetAccountInfo() { string url = URLHelpers.FixPrefix(APIURL); url = URLHelpers.CombineURL(APIURL, "account/info/?format=json"); .... } 

PVS-Studio Warning: V3008 La variable 'url' se voit attribuer des valeurs deux fois de suite. C'est peut-être une erreur. Vérifiez les lignes: 197, 196. Seafile.cs 197

Comme vous pouvez le voir dans l'exemple, lors de la déclaration de la variable url , une valeur renvoyée par la méthode FixPrefix lui est attribuée. Dans la ligne suivante, nous «broyons» la valeur résultante, même sans l'utiliser nulle part. Nous obtenons quelque chose de similaire au "code mort" - il fait le travail, il n'affecte pas le résultat final. Cette erreur est probablement le résultat du copier-coller, car de tels fragments de code se trouvent dans 9 autres méthodes.
Pour un exemple, nous donnons deux méthodes avec une première ligne similaire:

 public bool CheckAuthToken() { string url = URLHelpers.FixPrefix(APIURL); url = URLHelpers.CombineURL(APIURL, "auth/ping/?format=json"); .... } .... public bool CheckAPIURL() { string url = URLHelpers.FixPrefix(APIURL); url = URLHelpers.CombineURL(APIURL, "ping/?format=json"); .... } 

Total


Comme nous pouvons le voir, la complexité de la configuration de la vérification automatique par l'analyseur ne dépend pas du système CI sélectionné - en seulement 15 minutes et en quelques clics de souris, nous configurons la vérification de notre code de projet avec un analyseur statique.

En conclusion, nous vous suggérons de télécharger et d'essayer l'analyseur sur vos projets.



Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Oleg Andreev, Ilya Gainulin. PVS-Studio dans les nuages: Azure DevOps .

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


All Articles