Huawei Cloud: il fait nuageux dans PVS-Studio aujourd'hui

Image 2

Au cours de ce siècle, tout le monde a entendu parler des services cloud. De nombreuses entreprises ont maîtrisé ce segment de marché et créé leurs services cloud dans différentes directions. Notre équipe s'est également récemment intéressée à ces services en termes d'intégration de l'analyseur de code PVS-Studio avec eux. Nous pensons que nos lecteurs réguliers devinent déjà quel type de projet nous allons vérifier cette fois. Le choix s'est porté sur le code des services cloud de Huawei.

Présentation


Si vous suivez l'équipe PVS-Studio, vous avez probablement remarqué que récemment nous nous sommes beaucoup intéressés aux technologies cloud. Nous avons déjà publié plusieurs articles liés à ce sujet:


Et donc, quand je prenais un autre projet intéressant pour le prochain article, j'ai reçu une offre d'emploi de Huawei par la poste. Lors de la collecte d'informations sur cette société, il s'est avéré qu'ils avaient leurs propres services cloud, mais l'essentiel est que le code source de ces services est disponible gratuitement sur GitHub. C'était la principale raison du choix de cette entreprise pour cet article. Comme l'a dit un sage chinois: "Les accidents ne sont pas accidentels."

Maintenant, parlons un peu de notre analyseur. PVS-Studio est un analyseur de code statique qui identifie les erreurs et les vulnérabilités dans le code source des programmes écrits en C, C ++, C # et Java. L'analyseur fonctionne sur les plateformes Windows, Linux et macOS. En plus des plugins pour des environnements de développement classiques tels que Visual Studio ou IntelliJ IDEA, l'analyseur a la capacité de s'intégrer avec SonarQube et Jenkins:


Analyse de projet


Au cours de la recherche d'informations pour l'article, j'ai découvert que Huawei dispose d'un centre de développement où vous pouvez obtenir des informations, des guides et le code source de leurs services cloud. Divers langages de programmation ont été utilisés pour créer ces services, mais des langages tels que Go, Java et Python se sont avérés être les plus populaires.

Comme je suis spécialisé en Java, les projets ont été choisis en conséquence. Les sources des projets analysés dans l'article peuvent être obtenues dans le référentiel GitHub huaweicloud .

Pour analyser les projets, je n'avais besoin d'effectuer que quelques actions:

  • Obtenez des projets à partir du référentiel;
  • Utilisez les instructions pour démarrer l'analyseur Java et démarrer l'analyse sur chacun des projets.

Après avoir analysé les projets, nous avons réussi à n'en distinguer que trois auxquels je voudrais prêter attention. Cela est dû au fait que la taille des projets Java restants était trop petite.

Résultats de l'analyse du projet (nombre d'avertissements et nombre de fichiers):


Il y a peu d'avertissements, donc en général on peut dire de la bonne qualité du code. De plus, toutes les opérations ne sont pas des erreurs valides. Cela est dû au fait que l'analyseur n'a parfois pas suffisamment d'informations pour distinguer le code correct du code incorrect. Par conséquent, les diagnostics de l'analyseur sont améliorés chaque jour à l'aide des informations reçues des utilisateurs. Voir également l'article " Comment et pourquoi les analyseurs statiques combattent les faux positifs ".

Dans le processus d'analyse des projets, j'ai sélectionné les avertissements les plus intéressants, dont je parlerai dans cet article.

Ordre d'initialisation des champs


Le cycle d'initialisation de la classe V6050 est présent. L'initialisation de 'INSTANCE' apparaît avant l'initialisation de 'LOG'. UntrustedSSL.java (32), UntrustedSSL.java (59), UntrustedSSL.java (33)

public class UntrustedSSL { private static final UntrustedSSL INSTANCE = new UntrustedSSL(); private static final Logger LOG = LoggerFactory.getLogger(UntrustedSSL.class); .... private UntrustedSSL() { try { .... } catch (Throwable t) { LOG.error(t.getMessage(), t); // <= } } } 

Si une exception se produit dans le constructeur de la classe UntrustedSSL , les informations sur cette exception sont enregistrées dans le bloc catch à l'aide de l'enregistreur LOG . Cependant, en raison de l'ordre d'initialisation des champs statiques, le LOG au moment de l'initialisation du champ INSTANCE n'est pas encore initialisé. Par conséquent, lors de la journalisation des informations d'exception dans le constructeur, une exception NullPointerException sera levée. Cette exception est la cause d'une autre exception ExceptionInInitializerError qui est levée si une exception s'est produite lors de l'initialisation du champ statique. Tout ce qui est nécessaire pour résoudre le problème décrit est de placer l'initialisation LOG avant l'initialisation INSTANCE .

Faute de frappe invisible


V6005 La variable 'this.metricSchema' est assignée à elle-même. OpenTSDBSchema.java (72)

 public class OpenTSDBSchema { @JsonProperty("metric") private List<SchemaField> metricSchema; .... public void setMetricsSchema(List<SchemaField> metricsSchema) { this.metricSchema = metricSchema; // <= } public void setMetricSchema(List<SchemaField> metricSchema) { this.metricSchema = metricSchema; } .... } 

Les deux méthodes définissent le champ metricSchema , mais les noms de méthode diffèrent par leur caractère. Le programmeur a nommé les arguments de ces méthodes en fonction du nom de la méthode. Par conséquent, dans la ligne pointée par l'analyseur, le champ metricSchema est assigné à lui-même et l'argument de la méthode metricsSchema n'est pas utilisé.

V6005 La variable 'suspend' est assignée à elle-même. SuspendTransferTaskRequest.java (77)

 public class SuspendTransferTaskRequest { .... private boolean suspend; .... public void setSuspend(boolean suspend) { suspend = suspend; } .... } 

Voici une erreur banale liée à la négligence, à cause de laquelle l'affectation de l'argument suspendre à lui-même se produit. Par conséquent, la valeur de l'argument arrivé ne sera pas affectée au champ de suspension , comme implicite. Correctement:

 public void setSuspend(boolean suspend) { this.suspend = suspend; } 

Conditions prédéterminées


Comme cela arrive souvent, la règle V6007 prend de l' avance en termes de nombre d'avertissements.

V6007 L' expression «firewallPolicyId == null» est toujours fausse. FirewallPolicyServiceImpl.java (125)

 public FirewallPolicy removeFirewallRuleFromPolicy(String firewallPolicyId, String firewallRuleId) { checkNotNull(firewallPolicyId); checkNotNull(firewallRuleId); checkState(!(firewallPolicyId == null && firewallRuleId == null), "Either a Firewall Policy or Firewall Rule identifier must be set"); .... } 

Dans cette méthode, les arguments sont vérifiés pour null par la méthode checkNotNull :

 @CanIgnoreReturnValue public static <T> T checkNotNull(T reference) { if (reference == null) { throw new NullPointerException(); } else { return reference; } } 

Après avoir vérifié l'argument avec la méthode checkNotNull , vous pouvez être sûr à 100% que l'argument passé à cette méthode n'est pas nul . Étant donné que les deux arguments de la méthode removeFirewallRuleFromPolicy sont vérifiés par la méthode checkNotNull , il est inutile de vérifier à nouveau les arguments pour les valeurs nulles . Cependant, une expression est passée à la méthode checkState comme premier argument, où les arguments firewallPolicyId et firewallRuleId sont revérifiés pour null .

Un avertissement similaire est émis pour firewallRuleId :

  • V6007 L'expression 'firewallRuleId == null' est toujours fausse. FirewallPolicyServiceImpl.java (125)

V6007 L' expression 'filteringParams! = Null' est toujours vraie. NetworkPolicyServiceImpl.java (60)

 private Invocation<NetworkServicePolicies> buildInvocation(Map<String, String> filteringParams) { .... if (filteringParams == null) { return servicePoliciesInvocation; } if (filteringParams != null) { // <= .... } return servicePoliciesInvocation; } 

Dans cette méthode, si l'argument filteringParams est nul , la méthode se ferme et renvoie une valeur. Par conséquent, le test pointé par l'analyseur sera toujours vrai, ce qui signifie que ce test n'a pas de sens.

La même chose se produit dans 13 classes:

  • V6007 L'expression 'filteringParams! = Null' est toujours vraie. PolicyRuleServiceImpl.java (58)
  • V6007 L'expression 'filteringParams! = Null' est toujours vraie. GroupServiceImpl.java (58)
  • V6007 L'expression 'filteringParams! = Null' est toujours vraie. ExternalSegmentServiceImpl.java (57)
  • V6007 L'expression 'filteringParams! = Null' est toujours vraie. L3policyServiceImpl.java (57)
  • V6007 L'expression 'filteringParams! = Null' est toujours vraie. PolicyRuleSetServiceImpl.java (58)
  • et ainsi de suite ...

Référence nulle


V6008 Déréférence nulle potentielle de 'm.blockDeviceMapping'. NovaServerCreate.java (390)

 @Override public ServerCreateBuilder blockDevice(BlockDeviceMappingCreate blockDevice) { if (blockDevice != null && m.blockDeviceMapping == null) { m.blockDeviceMapping = Lists.newArrayList(); } m.blockDeviceMapping.add(blockDevice); // <= return this; } 

Dans cette méthode, l'initialisation du champ de référence m.blockDeviceMapping ne se produira pas si l'argument blockDevice est nul . Ce champ est initialisé uniquement dans cette méthode, donc lorsque la méthode add est appelée, le champ m.blockDeviceMapping lèvera une NullPointerException .

V6008 Déréférence nulle potentielle de 'FileId.get (chemin)' dans la fonction '<init>'. TrackedFile.java (140), TrackedFile.java (115)

 public TrackedFile(FileFlow<?> flow, Path path) throws IOException { this(flow, path, FileId.get(path), ....); } 

Le résultat de la méthode statique FileId.get (chemin) est transmis au constructeur de la classe TrackedFile comme troisième argument. Mais cette méthode peut retourner null :

 public static FileId get(Path file) throws IOException { if (!Files.exists(file)) { return null; } .... } 

Dans le constructeur appelé par ce biais, l'argument id ne change pas tant qu'il n'est pas utilisé pour la première fois:

 public TrackedFile(...., ...., FileId id, ....) throws IOException { .... FileId newId = FileId.get(path); if (!id.equals(newId)) { .... } } 

Par conséquent, si null est passé à la méthode comme troisième argument, une exception se produit.

Une situation similaire se produit dans un autre cas:

  • V6008 Déréférence nulle potentielle de «tampon». PublishingQueue.java (518)

V6008 Déréférence nulle potentielle de 'dataTmpFile'. CacheManager.java (91)

 @Override public void putToCache(PutRecordsRequest putRecordsRequest) { .... if (dataTmpFile == null || !dataTmpFile.exists()) { try { dataTmpFile.createNewFile(); // <= } catch (IOException e) { LOGGER.error("Failed to create cache tmp file, return.", e); return ; } } .... } 

Et encore NPE. Un certain nombre de vérifications dans l'instruction conditionnelle permettent à l'objet dataTmpFile d'être null pour un déréférencement ultérieur. Je pense qu'il y a deux fautes de frappe ici, et le chèque devrait en fait être comme ceci:

 if (dataTmpFile != null && !dataTmpFile.exists()) 

Sous-chaînes et nombres négatifs


V6009 La fonction 'substring' pourrait recevoir la valeur '-1' alors qu'une valeur non négative est attendue. Inspectez l'argument: 2. RemoveVersionProjectIdFromURL.java (37)

 @Override public String apply(String url) { String urlRmovePojectId = url.substring(0, url.lastIndexOf("/")); return urlRmovePojectId.substring(0, urlRmovePojectId.lastIndexOf("/")); } 

Il est entendu que l'URL est transmise à cette méthode sous forme de chaîne, qui n'est en aucun cas validée. Après cette chaîne est découpée plusieurs fois à l'aide de la méthode lastIndexOf . Si lastIndexOf ne trouve pas de correspondance dans la chaîne, il renverra -1. Cela lèvera une StringIndexOutOfBoundsException , car les arguments de la méthode de sous - chaîne doivent être des nombres non négatifs. Pour que la méthode fonctionne correctement, vous devez ajouter la validation de l'argument d'entrée ou vérifier que les résultats des méthodes lastIndexOf ne sont pas des nombres négatifs.

Voici d'autres endroits où cela se produit:

  • V6009 La fonction 'substring' pourrait recevoir la valeur '-1' alors qu'une valeur non négative est attendue. Inspectez l'argument: 2. RemoveProjectIdFromURL.java (37)
  • V6009 La fonction 'substring' pourrait recevoir la valeur '-1' alors qu'une valeur non négative est attendue. Inspectez l'argument: 2. RemoveVersionProjectIdFromURL.java (38)

Résultat oublié


V6010 La valeur de retour de la fonction 'concat' doit être utilisée. AKSK.java (278)

 public static String buildCanonicalHost(URL url) { String host = url.getHost(); int port = url.getPort(); if (port > -1) { host.concat(":" + Integer.toString(port)); } return host; } 

Lors de l'écriture de ce code, il n'a pas été pris en compte que l'appel de la méthode concat ne changera pas la chaîne hôte en raison de l'immuabilité des objets de type String . Pour que la méthode fonctionne correctement, vous devez affecter le résultat de la méthode concat à la variable hôte dans le bloc if . Correctement:

 if (port > -1) { host = host.concat(":" + Integer.toString(port)); } 

Variables inutilisées


V6021 L'URL variable n'est pas utilisée. TriggerV2Service.java (95)

 public ActionResponse deleteAllTriggersForFunction(String functionUrn) { checkArgument(!Strings.isNullOrEmpty(functionUrn), ....); String url = ClientConstants.FGS_TRIGGERS_V2 + ClientConstants.URI_SEP + functionUrn; return deleteWithResponse(uri(triggersUrlFmt, functionUrn)).execute(); } 

Dans cette méthode, la variable url n'est pas utilisée après l'initialisation. Très probablement, la variable url doit être passée à la méthode uri comme deuxième argument au lieu de functionUrn , car la variable functionUrn est impliquée dans l'initialisation de la variable url .

Argument non utilisé dans le constructeur


V6022 Le paramètre 'returnType' n'est pas utilisé dans le corps du constructeur. HttpRequest.java (68)

 public class HttpReQuest<R> { .... Class<R> returnType; .... public HttpRequest(...., Class<R> returnType) // <= { this.endpoint = endpoint; this.path = path; this.method = method; this.entity = entity; } .... public Class<R> getReturnType() { return returnType; } .... } 

Dans ce constructeur, ils ont oublié d'utiliser l'argument returnType et d'assigner sa valeur au champ returnType . Par conséquent, lorsque la méthode getReturnType est appelée , l'objet créé par ce constructeur renverra la valeur par défaut null , bien qu'il était très probablement destiné à obtenir l'objet qui a été précédemment transmis au constructeur.

Même fonctionnalité


V6032 Il est étrange que le corps de la méthode 'enable' soit entièrement équivalent au corps d'une autre méthode 'disable'. ServiceAction.java (32), ServiceAction.java (36)

 public class ServiceAction implements ModelEntity { private String binary; private String host; private ServiceAction(String binary, String host) { this.binary = binary; this.host = host; } public static ServiceAction enable(String binary, String host) { // <= return new ServiceAction(binary, host); } public static ServiceAction disable(String binary, String host) { // <= return new ServiceAction(binary, host); } .... } 

La présence de deux méthodes identiques n'est pas une erreur, mais le fait que deux méthodes effectuent la même action est au moins étrange. En regardant les noms des méthodes ci-dessus, nous pouvons supposer qu'elles effectuent les actions opposées. En fait, les deux méthodes font la même chose: elles créent et renvoient un objet ServiceAction . Très probablement, la méthode disable a été créée en copiant le code de la méthode enable , mais a oublié de changer le corps de la méthode.

J'ai oublié de vérifier la chose principale


V6060 La référence "params" a été utilisée avant d'être vérifiée par rapport à null. DomainService.java (49), DomainService.java (46)

 public Domains list(Map<String, String> params) { Preconditions.checkNotNull(params.get("page_size"), ....); Preconditions.checkNotNull(params.get("page_number"), ....); Invocation<Domains> domainInvocation = get(Domains.class, uri("/domains")); if (params != null) { // <= .... } return domainInvocation.execute(this.buildExecutionOptions(Domains.class)); } 

Dans cette méthode, nous avons décidé de vérifier le contenu d'une structure de type Map pour l'inégalité nulle . Pour ce faire, l'argument params appelle deux fois la méthode get , dont le résultat est passé à la méthode checkNotNull . Tout semble logique, mais peu importe comment! Dans if , l'argument params est vérifié pour null . Après quoi, on peut supposer que l'argument d'entrée peut être nul , mais avant cette vérification, la méthode get était déjà appelée deux fois sur les paramètres . Si null est passé comme argument à cette méthode, la première fois que la méthode get est appelée, une exception sera levée.

Une situation similaire se produit à trois autres endroits:

  • V6060 La référence "params" a été utilisée avant d'être vérifiée par rapport à null. DomainService.java (389), DomainService.java (387)
  • V6060 La référence "params" a été utilisée avant d'être vérifiée par rapport à null. DomainService.java (372), DomainService.java (369)
  • V6060 La référence "params" a été utilisée avant d'être vérifiée par rapport à null. DomainService.java (353), DomainService.java (350)

Conclusion


De nos jours, les grandes entreprises modernes ne peuvent tout simplement pas se passer de l'utilisation des services cloud. Un grand nombre de personnes utilisent ces services, donc même la moindre erreur dans le service peut entraîner des problèmes pour de nombreuses personnes, ainsi que des coûts supplémentaires pour l'entreprise afin de corriger les conséquences de cette erreur. Lors du développement, il est toujours nécessaire de prendre en compte le facteur humain, car toute personne fait tôt ou tard des erreurs, et cet article en est un exemple. C'est pourquoi vous devez utiliser tous les outils possibles pour améliorer la qualité du code.

PVS-Studio informera certainement Huawei des résultats de la vérification de leurs services cloud afin que les développeurs de cette société puissent les étudier plus en détail.

L'utilisation ponctuelle de l'analyse de code statique démontrée dans l'article ne peut pas montrer tous ses avantages. Des informations plus détaillées sur la façon d'utiliser correctement l'analyse statique peuvent être trouvées ici et ici . Vous pouvez télécharger l'analyseur PVS-Studio ici .



Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Valery Komarov. Huawei Cloud: c'est nuageux dans PVS-Studio aujourd'hui .

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


All Articles