Analyse du code de la plateforme CUBA avec PVS-Studio


Les développeurs Java ont accès à un certain nombre d'outils utiles qui aident à écrire du code de haute qualité tels que le puissant IDE IntelliJ IDEA, les analyseurs gratuits SpotBugs, PMD, etc. Les développeurs travaillant sur la plate-forme CUBA ont déjà utilisé tout cela, et cette revue montrera comment le projet peut bénéficier encore plus de l'utilisation de l'analyseur de code statique PVS-Studio.

Quelques mots sur le projet et l'analyseur


La plateforme CUBA est un cadre de haut niveau pour le développement d'applications d'entreprise. La plateforme fait abstraction des développeurs des technologies sous-jacentes afin qu'ils puissent se concentrer sur les tâches métier, tout en conservant une flexibilité totale en offrant un accès illimité au code de bas niveau. Le code source a été téléchargé depuis GitHub .

PVS-Studio est un outil pour détecter les bogues et les failles de sécurité potentielles dans le code source des programmes écrits en C, C ++, C # et Java. L'analyseur fonctionne sur les systèmes Windows, Linux et macOS 64 bits. Pour faciliter les choses pour les programmeurs Java, nous avons développé des plugins pour Maven, Gradle et IntelliJ IDEA. J'ai vérifié le projet en utilisant le plugin Gradle, et il s'est déroulé sans accroc.

Erreurs dans les conditions


Avertissement 1

V6007 L' expression 'StringUtils.isNotEmpty ("handleTabKey")' est toujours vraie. SourceCodeEditorLoader.java (60)

@Override public void loadComponent() { .... String handleTabKey = element.attributeValue("handleTabKey"); if (StringUtils.isNotEmpty("handleTabKey")) { resultComponent.setHandleTabKey(Boolean.parseBoolean(handleTabKey)); } .... } 

La valeur d'attribut extraite de l'élément n'est pas vérifiée. Au lieu de cela, la fonction isNotEmpty obtient un littéral de chaîne comme argument plutôt que la variable handleTabKey .

Une erreur similaire a été trouvée dans le fichier AbstractTableLoader.java:

  • V6007 L'expression 'StringUtils.isNotEmpty ("editable")' est toujours vraie. AbstractTableLoader.java (596)

Avertissement 2

V6007 L' expression 'previousMenuItemFlatIndex> = 0' est toujours vraie. CubaSideMenuWidget.java (328)

 protected MenuItemWidget findNextMenuItem(MenuItemWidget currentItem) { List<MenuTreeNode> menuTree = buildVisibleTree(this); List<MenuItemWidget> menuItemWidgets = menuTreeToList(menuTree); int menuItemFlatIndex = menuItemWidgets.indexOf(currentItem); int previousMenuItemFlatIndex = menuItemFlatIndex + 1; if (previousMenuItemFlatIndex >= 0) { return menuItemWidgets.get(previousMenuItemFlatIndex); } return null; } 

La fonction indexOf retournera -1 si l'élément n'est pas trouvé dans la liste. La valeur 1 est ensuite ajoutée à l'index, ce qui masque le problème avec l'élément absent. Un autre problème potentiel est lié au fait que la variable précédenteMenuItemFlatIndex sera toujours supérieure ou égale à zéro. Par exemple, si la liste menuItemWidgets s'avère vide, le programme se retrouvera avec un dépassement de tableau.

Avertissement 3

V6009 La fonction 'supprimer' pourrait recevoir la valeur '-1' alors qu'une valeur non négative est attendue. Inspectez l'argument: 1. AbstractCollectionDatasource.java (556)

 protected DataLoadContextQuery createDataQuery(....) { .... StringBuilder orderBy = new StringBuilder(); .... if (orderBy.length() > 0) { orderBy.delete(orderBy.length() - 2, orderBy.length()); orderBy.insert(0, " order by "); } .... } 

Les deux derniers caractères du tampon orderBy sont supprimés si le nombre total d'éléments est supérieur à zéro, c'est-à-dire si la chaîne contient au moins un caractère. Cependant, la position de départ à partir de laquelle la suppression commence est décalée de 2. Ainsi, si orderBy contient un caractère, la tentative de suppression déclenchera une StringIndexOutOfBoundsException .

Avertissement 4

V6013 Les objets 'masterCollection' et 'entités' sont comparés par référence. Une comparaison de l'égalité était peut-être envisagée. CollectionPropertyContainerImpl.java (81)

 @Override public void setItems(@Nullable Collection<E> entities) { super.setItems(entities); Entity masterItem = master.getItemOrNull(); if (masterItem != null) { MetaProperty masterProperty = getMasterProperty(); Collection masterCollection = masterItem.getValue(masterProperty.getName()); if (masterCollection != entities) { updateMasterCollection(masterProperty, masterCollection, entities); } } } 

Dans la fonction updateMasterCollection , les valeurs des entités sont copiées dans masterCollection . Une ligne plus tôt, les collections ont été comparées par référence, mais le programmeur a probablement voulu que ce soit une comparaison par valeur.

Avertissement 5

V6013 Les objets 'value' et 'oldValue' sont comparés par référence. Une comparaison de l'égalité était peut-être envisagée. WebOptionsList.java (278)

 protected boolean isCollectionValuesChanged(Collection<I> value, Collection<I> oldValue) { return value != oldValue; } 

Ce cas est similaire au précédent. Les collections sont comparées dans la fonction isCollectionValuesChanged , et la comparaison de référence n'est peut-être pas non plus ce qui était prévu ici.

Conditions redondantes


Avertissement 1

V6007 L' expression «mask.charAt (i + offset)! = PlaceHolder» est toujours vraie. DatePickerDocument.java (238)

 private String calculateFormattedString(int offset, String text) .... { .... if ((mask.charAt(i + offset) == placeHolder)) { // <= .... } else if ((mask.charAt(i + offset) != placeHolder) && // <= (Character.isDigit(text.charAt(i)))) { .... } .... } 

La deuxième condition vérifie une expression opposée à celle vérifiée dans la première condition. Ce dernier peut donc être supprimé en toute sécurité pour raccourcir le code.

V6007 L' expression 'connecteur == null' est toujours fausse. HTML5Support.java (169)

 private boolean validate(NativeEvent event) { .... while (connector == null) { widget = widget.getParent(); connector = Util.findConnectorFor(widget); } if (this.connector == connector) { return true; } else if (connector == null) { // <= return false; } else if (connector.getWidget() instanceof VDDHasDropHandler) { return false; } return true; } 

Après avoir quitté la boucle while, la valeur de la variable de connecteur ne sera pas égale à null , donc le contrôle redondant peut être supprimé.

Un autre avertissement suspect de ce type qui doit être examiné:

  • V6007 L'expression 'StringUtils.isBlank (strValue)' est toujours vraie. Param.java (818)

Code inaccessible dans les tests


V6019 Code inaccessible détecté. Il est possible qu'une erreur soit présente. TransactionTest.java (283)

 private void throwException() { throw new RuntimeException(TEST_EXCEPTION_MSG); } @Test public void testSuspendRollback() { Transaction tx = cont.persistence().createTransaction(); try { .... Transaction tx1 = cont.persistence().createTransaction(); try { EntityManager em1 = cont.persistence().getEntityManager(); assertTrue(em != em1); Server server1 = em1.find(Server.class, server.getId()); assertNull(server1); throwException(); // <= tx1.commit(); // <= } catch (Exception e) { // } finally { tx1.end(); } tx.commit(); } finally { tx.end(); } } 

La fonction throwException lève une exception qui empêche l'exécution de l'appel de tx1.commit . Ces deux lignes doivent être échangées pour que le code fonctionne correctement.

Il y avait aussi quelques problèmes similaires dans d'autres tests:

  • V6019 Code inaccessible détecté. Il est possible qu'une erreur soit présente. TransactionTest.java (218)
  • V6019 Code inaccessible détecté. Il est possible qu'une erreur soit présente. TransactionTest.java (163)
  • V6019 Code inaccessible détecté. Il est possible qu'une erreur soit présente. TransactionTest.java (203)
  • V6019 Code inaccessible détecté. Il est possible qu'une erreur soit présente. TransactionTest.java (137)
  • V6019 Code inaccessible détecté. Il est possible qu'une erreur soit présente. UpdateDetachedTest.java (153)
  • V6019 Code inaccessible détecté. Il est possible qu'une erreur soit présente. EclipseLinkDetachedTest.java (132)
  • V6019 Code inaccessible détecté. Il est possible qu'une erreur soit présente. PersistenceTest.java (223)

Arguments suspects


Avertissement 1

V6023 Le paramètre 'salt' est toujours réécrit dans le corps de la méthode avant d'être utilisé. BCryptEncryptionModule.java (47)

 @Override public String getHash(String content, String salt) { salt = BCrypt.gensalt(); return BCrypt.hashpw(content, salt); } 

En cryptographie, salt est une chaîne de données que vous transmettez avec le mot de passe à une fonction de hachage. Il est principalement utilisé pour protéger le programme contre les attaques de dictionnaire et les attaques de table arc-en-ciel, ainsi que pour masquer des mots de passe identiques. Plus ici: Salt (cryptographie) .

Dans cette fonction, la chaîne transmise est remplacée juste après la saisie. Ignorer la valeur transmise à la fonction est une vulnérabilité potentielle.

Avertissement 2

Cette fonction a déclenché deux avertissements à la fois:

  • V6023 Le paramètre 'offsetWidth' est toujours réécrit dans le corps de la méthode avant d'être utilisé. CubaSuggestionFieldWidget.java (433)
  • V6023 Le paramètre 'offsetHeight' est toujours réécrit dans le corps de la méthode avant d'être utilisé. CubaSuggestionFieldWidget.java (433)

 @Override public void setPosition(int offsetWidth, int offsetHeight) { offsetHeight = getOffsetHeight(); .... if (offsetHeight + getPopupTop() > ....)) { .... } .... offsetWidth = containerFirstChild.getOffsetWidth(); if (offsetWidth + getPopupLeft() > ....)) { .... } else { left = getPopupLeft(); } setPopupPosition(left, top); } 

C'est un extrait assez curieux. La fonction est appelée avec seulement deux variables comme arguments, offsetWidth et offsetHeight , et les deux sont écrasées avant utilisation.

Avertissement 3

V6022 Le paramètre 'raccourci' n'est pas utilisé dans le corps du constructeur. DeclarativeTrackingAction.java (47)

 public DeclarativeTrackingAction(String id, String caption, String description, String icon, String enable, String visible, String methodName, @Nullable String shortcut, ActionsHolder holder) { super(id); this.caption = caption; this.description = description; this.icon = icon; setEnabled(enable == null || Boolean.parseBoolean(enable)); setVisible(visible == null || Boolean.parseBoolean(visible)); this.methodName = methodName; checkActionsHolder(holder); } 

La fonction n'utilise pas la valeur passée comme paramètre de raccourci . L'interface de la fonction est peut-être devenue obsolète, ou cet avertissement n'est qu'un faux positif.

Encore quelques défauts de ce type:

  • V6022 Le paramètre 'type' n'est pas utilisé dans le corps du constructeur. QueryNode.java (36)
  • V6022 Le paramètre 'text2' n'est pas utilisé dans le corps du constructeur. MarkerAddition.java (22)
  • V6022 Le paramètre 'sélection' n'est pas utilisé à l'intérieur du corps du constructeur. AceEditor.java (114)
  • V6022 Le paramètre 'options' n'est pas utilisé dans le corps du constructeur. EntitySerialization.java (379)

Différentes fonctions, même code


Avertissement 1

V6032 Il est étrange que le corps de la méthode 'firstItemId' soit entièrement équivalent au corps d'une autre méthode 'lastItemId'. ContainerTableItems.java (213), ContainerTableItems.java (219)
 @Override public Object firstItemId() { List<E> items = container.getItems(); return items.isEmpty() ? null : items.get(0).getId(); } @Override public Object lastItemId() { List<E> items = container.getItems(); return items.isEmpty() ? null : items.get(0).getId(); } 

Les fonctions firstItemId et lastItemId ont les mêmes implémentations. Ce dernier était probablement censé obtenir l'index du dernier élément plutôt que d'obtenir l'élément à l'index 0.

Avertissement 2

V6032 Il est étrange que le corps de la méthode soit entièrement équivalent au corps d'une autre méthode. SearchComboBoxPainter.java (495), SearchComboBoxPainter.java (501)

 private void paintBackgroundDisabledAndEditable(Graphics2D g) { rect = decodeRect1(); g.setPaint(color53); g.fill(rect); } private void paintBackgroundEnabledAndEditable(Graphics2D g) { rect = decodeRect1(); g.setPaint(color53); g.fill(rect); } 

Deux autres fonctions avec des corps étrangement identiques. Je suppose que l'un d'eux était censé fonctionner avec une autre couleur au lieu de color53 .

Déréférence nulle


Avertissement 1

V6060 La référence 'descriptionPopup' a été utilisée avant d'être vérifiée par rapport à null. SuggestPopup.java (252), SuggestPopup.java (251)

 protected void updateDescriptionPopupPosition() { int x = getAbsoluteLeft() + WIDTH; int y = getAbsoluteTop(); descriptionPopup.setPopupPosition(x, y); if (descriptionPopup!=null) { descriptionPopup.setPopupPosition(x, y); } } 

En seulement deux lignes, le programmeur a réussi à écrire un morceau de code très suspect. La méthode setPopupPosition de l'objet descriptionPopup est d'abord appelée, puis l'objet est vérifié pour null . Le premier appel à setPopupPosition est probablement redondant et potentiellement dangereux. Je suppose que cela résulte d'une mauvaise refactorisation.

Avertissement 2

V6060 La référence 'tableModel' a été utilisée avant d'être vérifiée par rapport à null. DesktopAbstractTable.java (1580), DesktopAbstractTable.java (1564)

 protected Column addRuntimeGeneratedColumn(String columnId) { // store old cell editors / renderers TableCellEditor[] cellEditors = new TableCellEditor[tableModel.getColumnCount() + 1]; // <= TableCellRenderer[] cellRenderers = new TableCellRenderer[tableModel.getColumnCount() + 1]; // <= for (int i = 0; i < tableModel.getColumnCount(); i++) { // <= Column tableModelColumn = tableModel.getColumn(i); if (tableModel.isGeneratedColumn(tableModelColumn)) { // <= TableColumn tableColumn = getColumn(tableModelColumn); cellEditors[i] = tableColumn.getCellEditor(); cellRenderers[i] = tableColumn.getCellRenderer(); } } Column col = new Column(columnId, columnId); col.setEditable(false); columns.put(col.getId(), col); if (tableModel != null) { // <= tableModel.addColumn(col); } .... } 

Ce cas est similaire au précédent. Au moment où l'objet tableModel est vérifié pour null , il a déjà été consulté plusieurs fois.

Un autre exemple:

  • V6060 La référence 'tableModel' a été utilisée avant d'être vérifiée par rapport à null. DesktopAbstractTable.java (596), DesktopAbstractTable.java (579)

Probablement une erreur logique


V6026 Cette valeur est déjà affectée à la variable 'sortAscending'. CubaScrollTableWidget.java (488)

 @Override protected void sortColumn() { .... if (sortAscending) { if (sortClickCounter < 2) { // special case for initial revert sorting instead of reset sort order if (sortClickCounter == 0) { client.updateVariable(paintableId, "sortascending", false, false); } else { reloadDataFromServer = false; sortClickCounter = 0; sortColumn = null; sortAscending = true; // <= client.updateVariable(paintableId, "resetsortorder", "", true); } } else { client.updateVariable(paintableId, "sortascending", false, false); } } else { if (sortClickCounter < 2) { // special case for initial revert sorting instead of reset sort order if (sortClickCounter == 0) { client.updateVariable(paintableId, "sortascending", true, false); } else { reloadDataFromServer = false; sortClickCounter = 0; sortColumn = null; sortAscending = true; client.updateVariable(paintableId, "resetsortorder", "", true); } } else { reloadDataFromServer = false; sortClickCounter = 0; sortColumn = null; sortAscending = true; client.updateVariable(paintableId, "resetsortorder", "", true); } } .... } 

Dans la première condition, la variable sortAscending s'est déjà vu attribuer la valeur true , mais elle lui est encore affectée ultérieurement. Ce doit être une erreur, et l'auteur a probablement voulu dire la valeur false .

Un exemple similaire à partir d'un fichier différent:

  • V6026 Cette valeur est déjà affectée à la variable 'sortAscending'. CubaTreeTableWidget.java (444)

Étranges valeurs de retour


Avertissement 1

V6037 Un «retour» inconditionnel dans une boucle. QueryCacheManager.java (128)

 public <T> T getSingleResultFromCache(QueryKey queryKey, List<View> views) { .... for (Object id : queryResult.getResult()) { return (T) em.find(metaClass.getJavaClass(), id, views.toArray(....)); } .... } 

L'analyseur a détecté un appel inconditionnel à retourner à la toute première itération d'une boucle for . Soit cette ligne est incorrecte, soit la boucle doit être réécrite en tant qu'instruction if .

Avertissement 2

V6014 Il est étrange que cette méthode renvoie toujours une seule et même valeur. DefaultExceptionHandler.java (40)

 @Override public boolean handle(ErrorEvent event, App app) { Throwable t = event.getThrowable(); if (t instanceof SocketException || ExceptionUtils.getRootCause(t) instanceof SocketException) { return true; } if (ExceptionUtils.getThrowableList(t).stream() .anyMatch(o -> o.getClass().getName().equals("...."))) { return true; } if (StringUtils.contains(ExceptionUtils.getMessage(t), "....")) { return true; } AppUI ui = AppUI.getCurrent(); if (ui == null) { return true; } if (t != null) { if (app.getConnection().getSession() != null) { showDialog(app, t); } else { showNotification(app, t); } } return true; } 

Cette fonction renvoie true dans chaque cas, tandis que la dernière ligne appelle évidemment false . Cela ressemble à une erreur.

Voici une liste complète d'autres fonctions suspectes similaires:

  • V6014 Il est étrange que cette méthode renvoie toujours une seule et même valeur. ErrorNodesFinder.java (31)
  • V6014 Il est étrange que cette méthode renvoie toujours une seule et même valeur. FileDownloadController.java (69)
  • V6014 Il est étrange que cette méthode renvoie toujours une seule et même valeur. IdVarSelector.java (73)
  • V6014 Il est étrange que cette méthode renvoie toujours une seule et même valeur. IdVarSelector.java (48)
  • V6014 Il est étrange que cette méthode renvoie toujours une seule et même valeur. IdVarSelector.java (67)
  • V6014 Il est étrange que cette méthode renvoie toujours une seule et même valeur. IdVarSelector.java (46)
  • V6014 Il est étrange que cette méthode renvoie toujours une seule et même valeur. JoinVariableNode.java (57)

Avertissement 3

V6007 L' expression 'needReload' est toujours fausse. WebAbstractTable.java (2702)

 protected boolean handleSpecificVariables(Map<String, Object> variables) { boolean needReload = false; if (isUsePresentations() && presentations != null) { Presentations p = getPresentations(); if (p.getCurrent() != null && p.isAutoSave(p.getCurrent()) && needUpdatePresentation(variables)) { Element e = p.getSettings(p.getCurrent()); saveSettings(e); p.setSettings(p.getCurrent(), e); } } return needReload; } 

La fonction renvoie la variable needReload dont la valeur est toujours false . Il manque probablement du code pour modifier cette valeur dans l'une des conditions.

Avertissement 4

V6062 Récursion infinie possible à l'intérieur de la méthode 'isFocused'. GwtAceEditor.java (189), GwtAceEditor.java (190)

 public final native void focus() /*-{ this.focus(); }-*/; public final boolean isFocused() { return this.isFocused(); } 

L'analyseur a détecté une fonction récursive sans condition d'arrêt. Ce fichier contient de nombreuses fonctions marquées du mot-clé native et contenant du code commenté. Les développeurs sont probablement en train de réécrire ce fichier et remarqueront bientôt la fonction isFocused .

Divers


Avertissement 1

V6002 L'instruction switch ne couvre pas toutes les valeurs de l'énumération 'Operation': ADD. DesktopAbstractTable.java (665)

 /** * Operation which caused the datasource change. */ enum Operation { REFRESH, CLEAR, ADD, REMOVE, UPDATE } @Override public void setDatasource(final CollectionDatasource datasource) { .... collectionChangeListener = e -> { switch (e.getOperation()) { case CLEAR: case REFRESH: fieldDatasources.clear(); break; case UPDATE: case REMOVE: for (Object entity : e.getItems()) { fieldDatasources.remove(entity); } break; } }; .... } 

L'instruction switch n'a pas de cas pour la valeur ADD . C'est la seule valeur qui n'est pas vérifiée, donc les développeurs devraient jeter un œil à ce code.

Avertissement 2

V6021 La «source» variable n'est pas utilisée. DefaultHorizontalLayoutDropHandler.java (177)

 @Override protected void handleHTML5Drop(DragAndDropEvent event) { LayoutBoundTransferable transferable = (LayoutBoundTransferable) event .getTransferable(); HorizontalLayoutTargetDetails details = (HorizontalLayoutTargetDetails) event .getTargetDetails(); AbstractOrderedLayout layout = (AbstractOrderedLayout) details .getTarget(); Component source = event.getTransferable().getSourceComponent(); // <= int idx = (details).getOverIndex(); HorizontalDropLocation loc = (details).getDropLocation(); if (loc == HorizontalDropLocation.CENTER || loc == HorizontalDropLocation.RIGHT) { idx++; } Component comp = resolveComponentFromHTML5Drop(event); if (idx >= 0) { layout.addComponent(comp, idx); } else { layout.addComponent(comp); } if (dropAlignment != null) { layout.setComponentAlignment(comp, dropAlignment); } } 

La source variable est déclarée mais non utilisée. Peut-être que les auteurs ont oublié d'ajouter la source à la mise en page , tout comme cela s'est produit avec une autre variable de ce type, comp .

Autres fonctions avec des variables inutilisées:

  • V6021 La «source» variable n'est pas utilisée. DefaultHorizontalLayoutDropHandler.java (175)
  • V6021 La valeur est affectée à la variable 'r' mais n'est pas utilisée. ExcelExporter.java (262)
  • V6021 La variable 'over' n'est pas utilisée. DefaultCssLayoutDropHandler.java (49)
  • V6021 La variable «transférable» n'est pas utilisée. DefaultHorizontalLayoutDropHandler.java (171)
  • V6021 La variable «transférable» n'est pas utilisée. DefaultHorizontalLayoutDropHandler.java (169)
  • V6021 La variable 'beanLocator' n'est pas utilisée. ScreenEventMixin.java (28)

Avertissement 3

Les classes V6054 ne doivent pas être comparées par leur nom. MessageTools.java (283)

 public boolean hasPropertyCaption(MetaProperty property) { Class<?> declaringClass = property.getDeclaringClass(); if (declaringClass == null) return false; String caption = getPropertyCaption(property); int i = caption.indexOf('.'); if (i > 0 && declaringClass.getSimpleName().equals(caption.substring(0, i))) return false; else return true; } 

L'analyseur a détecté une comparaison de classes par nom. Il est incorrect de comparer les classes par nom car, selon la spécification, les noms des classes JVM doivent être uniques uniquement dans un package. Une telle comparaison donne des résultats incorrects et conduit à exécuter le mauvais code.

Commentaire des développeurs de la plateforme CUBA


Tout grand projet contient sûrement des bogues. Sachant cela, nous sommes ravis lorsque l'équipe PVS-Studio a proposé de vérifier notre projet. Le référentiel CUBA contient des fourches de certaines des bibliothèques OSS tierces sous licence Apache 2, et il semble que nous devrions accorder plus d'attention à ce code car l'analyseur a trouvé un certain nombre de problèmes dans ces sources. Nous utilisons actuellement SpotBugs comme analyseur principal et il ne parvient pas à remarquer certains des gros bugs signalés par PVS-Studio. Il semble que nous devrions écrire nous-mêmes quelques diagnostics supplémentaires. Un grand merci à l'équipe PVS-Studio pour le travail.

Les développeurs nous ont également dit que les avertissements V6013 et V6054 étaient de faux positifs; c'était leur décision consciente d'écrire ce code comme ils l'ont fait. L'analyseur est conçu pour détecter les fragments de code suspects, et la probabilité de trouver de véritables bogues varie selon les différents diagnostics. Ces avertissements, cependant, peuvent être facilement gérés à l'aide du mécanisme spécial de suppression des avertissements de masse sans avoir à modifier les fichiers source.

De plus, l'équipe PVS-Studio ne peut pas ignorer la phrase "il semble que nous devrions écrire nous-mêmes quelques diagnostics supplémentaires" et nous passer de cette image :)

Image 3

Conclusion


PVS-Studio peut être un complément parfait aux outils de contrôle qualité existants utilisés dans votre processus de développement. Cela est particulièrement vrai pour les entreprises comptant des dizaines, des centaines ou des milliers de développeurs. PVS-Studio est conçu non seulement pour détecter les bogues mais aussi pour vous aider à les corriger, et ce que je veux dire par là n'est pas l'édition automatique de code mais des moyens fiables de contrôle de la qualité du code. Dans une grande entreprise, il est impossible pour chaque développeur de vérifier leurs parties respectives du code avec des outils différents, donc une meilleure solution pour ces entreprises serait d'adopter des outils comme PVS-Studio, qui fournissent un contrôle de la qualité du code à chaque étape du développement, pas uniquement du côté programmeur.

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


All Articles