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.
"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.
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).
Entrez la commande suivante dans n'importe quel répertoire.
$ git clone https://github.com/facebook/infer.git
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
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. ** **
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
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
Tout d'abord, essayez RacerD en utilisant le code de violation affiché sur JCIP.
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.
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.
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
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.
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);
}
}
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
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