J'ai essayé l'outil d'analyse de bogue de parallélisme Java de Facebook "Racer D"

J'ai essayé l'outil d'analyse de bogue de parallélisme Java "RacerD" publié par Facebook en open source. Dans cet article, nous partagerons les deux points suivants.

Qu'est-ce que "Racer D"?

"RacerD" est un outil d'analyse de code statique pour les bogues de concurrence Java publié par Facebook en open source. Il est utilisé dans le cadre de la fonction de ** l'outil d'analyse de code statique "Infer" sorti en open source en 2015 **. (Licence BSD à partir de février 2018)

Depuis mars 2018, il existe également Awesome Java, qui répertorie uniquement les outils Java soigneusement sélectionnés. , Infer est introduit.

Facebook a déjà intégré RacerD en tant que CI (Continuous Integration), et sa version Android de l'application a détecté plus de 1000 bogues parallèles.


Installer Infer

Tout d'abord, installez l'outil d'analyse de code statique "Infer" (la version au moment de la rédaction de cet article est la 0.13.1).

Cloner le dépôt git

Entrez la commande suivante dans n'importe quel répertoire.

$ git clone https://github.com/facebook/infer.git

Réécrire Dockerfile

Infer prend en charge non seulement Java mais aussi C, C ++, Objective-C (cependant, RacerD est uniquement Java). Dans cet article, nous réécrirons le Dockerfile car il suffit d'avoir l'environnement requis pour l'analyse du code statique Java.

Accédez au répertoire contenant les fichiers d'installation de l'image Docker.

$ cd infer/docker

La structure des répertoires est la suivante.

$ tree
.
├── Dockerfile
├── README.md
└── run.sh

Réécrivez le Dockerfile comme suit.

** Différence entre Dockerfile.origin (avant modification) et Dockerfile (après modification) **

$ diff -u Dockerfile.origin Dockerfile
--- Dockerfile.origin	2018-02-22 09:47:10.504984404 +0000
+++ Dockerfile	2018-02-22 09:47:46.533118963 +0000
@@ -31,17 +31,8 @@

 # Compile Infer
 RUN OCAML_VERSION=4.05.0+flambda; \
-    cd /infer && ./build-infer.sh --opam-switch $OCAML_VERSION && rm -rf /root/.opam
+    cd /infer && ./build-infer.sh java --yes --opam-switch $OCAML_VERSION && rm -rf /root/.opam

 # Install Infer
 ENV INFER_HOME /infer/infer
 ENV PATH ${INFER_HOME}/bin:${PATH}
-
-ENV ANDROID_HOME /opt/android-sdk-linux
-WORKDIR $ANDROID_HOME
-RUN curl -o sdk-tools-linux.zip \
-      https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip && \
-    unzip sdk-tools-linux.zip && \
-    rm sdk-tools-linux.zip
-ENV PATH ${ANDROID_HOME}/tools/bin:${PATH}
-RUN echo "sdk.dir=${ANDROID_HOME}" > /infer/examples/android_hello/local.properties

Installez l'image Docker

Exécutez le shell suivant pour installer l'image Docker.

./run.sh

Dans l'environnement utilisé dans cet article ** L'installation a pris environ 20 minutes. ** **

Vérifiez le fonctionnement d'Infer

Créez un conteneur à partir de l'image Docker d'Infer en entrant la commande suivante:

$ docker run -v /home/user/project:/mnt --rm -it infer

Running Infer in Docker · facebook/infer Wiki · GitHub

Accédez au répertoire monté suivant.

# cd mnt

Créez «Hello.java» dans le répertoire «mnt» de la page Web Infer (Hello, World! | Infer) ..

Vous pouvez analyser le code statique de Hello.java en entrant la commande suivante.

# infer run -- javac Hello.java
Capturing in javac mode...
Found 1 source file to analyze in /mnt/infer-out
Starting analysis...

legend:
  "F" analyzing a file
  "." analyzing a procedure

F..

Found 1 issue

Hello.java:5: error: NULL_DEREFERENCE
  object `s` last assigned on line 4 could be null and is dereferenced at line 5.
  3.       int test() {
  4.           String s = null;
  5. >         return s.length();
  6.       }
  7.   }


Summary of the reports

  NULL_DEREFERENCE: 1

Essayez RacerD avec du code pour le bogue de parallélisme

Maintenant, je voudrais commencer l'analyse du bug de parallélisme, qui est le sujet principal.

RacerD effectue une analyse statique sur le code Java en utilisant ** "lock" ou "@ThreadSafe annotation" **. Vous pouvez télécharger le fichier jar d'annotation @ThreadSafe à partir du site Web de JCIP (Java Concurrency in Practice).

Dans cet article, j'ai créé l'exemple de code suivant dans le répertoire mnt.

.
|-- Hello.java
|-- LongContainer.java
|-- UnsafeSequence.java
|-- jcip-annotations.jar

Exemple 1. Méthode de génération de séquence non thread-safe

Tout d'abord, essayez RacerD en utilisant le code de violation affiché sur JCIP.

Code de violation

Java Concurrency in Practice - Code Listings

package net.jcip.examples;

import net.jcip.annotations.*;

/**
 * UnsafeSequence
 *
 * @author Brian Goetz and Tim Peierls
 */

@ThreadSafe
public class UnsafeSequence {
    private int value;

    /**
     * Returns a unique value.
     */
    public int getNext() {
        return value++;
    }
}

Le problème avec le code ci-dessus est que si le timing n'est pas correct, les deux threads qui appellent getNext recevront ** la même valeur **. Une description d'incrément comme nextValue ++ ressemble à une opération, mais elle effectue en fait les trois opérations suivantes.

  1. Lisez la valeur
  2. Ajouter 1
  3. Écrivez la nouvelle valeur

Par conséquent, en fonction de la synchronisation de plusieurs threads Si deux threads lisent la même valeur en même temps et ajoutent 1 de la même manière, il est possible que les deux threads renvoient la même valeur.

Résultat de l'analyse: "THREAD_SAFETY_VIOLATION" détecté

infer --racerd-only -- javac -classpath jcip-annotations.jar UnsafeSequence.java
Capturing in javac mode...
Found 1 source file to analyze in /mnt/infer-out
Starting analysis...

legend:
  "F" analyzing a file
  "." analyzing a procedure

F..

Found 1 issue

UnsafeSequence.java:19: error: THREAD_SAFETY_VIOLATION
  Unprotected write. Non-private method `net.jcip.examples.UnsafeSequence.getNext` writes to field `&this.net.jcip.examples.UnsafeSequence.value` outside of synchronization.
 Reporting because the current class is annotated `@ThreadSafe`, so we assume that this method can run in parallel with other non-private methods in the class (incuding itself).
  17.        */
  18.       public int getNext() {
  19. >         return value++;
  20.       }
  21.   }


Summary of the reports

  THREAD_SAFETY_VIOLATION: 1

Exemple 2. Lecture et écriture de valeurs 64 bits

VNA05-J. Lire et écrire des valeurs 64 bits de manière atomique

Dans le modèle de mémoire Java du langage de programmation **, une seule écriture sur une valeur longue ou double non volatile est traitée comme deux écritures de 32 bits chacune. ** En conséquence, le thread peut voir la combinaison des 32 premiers bits d'une écriture de valeur 64 bits et des 32 bits suivants d'une autre écriture.

En raison de ce comportement, des valeurs indéterminées peuvent être lues dans le code requis pour être thread-safe. Par conséquent, les programmes multithread doivent garantir que les valeurs 64 bits sont lues et écrites de manière atomique.

Code de violation

Dans le code de violation suivant, si un thread appelle à plusieurs reprises la méthode assignValue () et qu'un autre thread appelle à plusieurs reprises la méthode printLong (), la méthode ** printLong () utilisera une valeur de i qui n'est ni 0 ni la valeur de l'argument j. Peut être sortie. ** **

import net.jcip.annotations.*;

@ThreadSafe
class LongContainer {
  private long i = 0;

  void assignValue(long j) {
    i = j;
  }

  void printLong() {
    System.out.println("i = " + i);
  }
}

Résultat de l'analyse: "THREAD_SAFETY_VIOLATION" détecté

infer --racerd-only -- javac -classpath jcip-annotations.jar LongContainer.java
Capturing in javac mode...
Found 1 source file to analyze in /mnt/infer-out
Starting analysis...

legend:
  "F" analyzing a file
  "." analyzing a procedure

F...

Found 2 issues

LongContainer.java:9: error: THREAD_SAFETY_VIOLATION
  Unprotected write. Non-private method `LongContainer.assignValue` writes to field `&this.LongContainer.i` outside of synchronization.
 Reporting because the current class is annotated `@ThreadSafe`, so we assume that this method can run in parallel with other non-private methods in the class (incuding itself).
  7.   
  8.     void assignValue(long j) {
  9. >     i = j;
  10.     }
  11.   

LongContainer.java:13: error: THREAD_SAFETY_VIOLATION
  Read/Write race. Non-private method `LongContainer.printLong` reads without synchronization from `&this.LongContainer.i`. Potentially races with writes in method `void LongContainer.assignValue(long)`.
 Reporting because the current class is annotated `@ThreadSafe`, so we assume that this method can run in parallel with other non-private methods in the class (incuding itself).
  11.   
  12.     void printLong() {
  13. >     System.out.println("i = " + i);
  14.     }
  15.   }


Summary of the reports

  THREAD_SAFETY_VIOLATION: 2

Résumé

Les classes que je souhaite utiliser pour threadsafe doivent être explicitement annotées avec @ ThreadSafe, mais j'ai trouvé que l'analyse des bogues de parallélisme par RacerD était assez efficace.

Recommended Posts

J'ai essayé l'outil d'analyse de bogue de parallélisme Java de Facebook "Racer D"
J'ai essayé Drools (Java, InputStream)
J'ai essayé d'utiliser Java REPL
J'ai essayé l'analyse du code source
Je ne suis pas sûr du traitement parallèle Java
J'ai essayé la métaprogrammation avec Java
J'ai essayé l'analyse morphologique avec MeCab
J'ai essayé d'interagir avec Java
J'ai essayé la communication UDP avec Java
J'ai essayé d'utiliser l'API Java8 Stream
J'ai essayé l'outil de micro-benchmarking de Java JMH
J'ai essayé d'utiliser JWT en Java
J'ai essayé de résumer Java 8 maintenant
J'ai essayé d'utiliser le mémo Java LocalDate
J'ai essayé d'utiliser Google HttpClient de Java
J'ai essayé d'utiliser l'API Elasticsearch en Java
J'ai essayé d'utiliser l'outil de diagnostic Java Arthas
J'ai essayé le mappeur d'objets de Cassandra pour Java
J'ai essayé de résumer les expressions Java lambda
J'ai essayé le nouveau yuan à Java
J'ai essayé d'utiliser OpenCV avec Java + Tomcat
J'ai essayé l'examen d'entrée de Google (non officiel) [java]