Ich habe das von Facebook als Open Source veröffentlichte Java-Parallelitäts-Fehleranalysetool "RacerD" ausprobiert. In diesem Artikel werden wir die folgenden zwei Punkte teilen.
--RacerD-Installationsverfahren (mit Docker)
"RacerD" ist ein statisches Code-Analyse-Tool für Java-Parallelitätsfehler, die von Facebook als Open Source veröffentlicht wurden. Wird als Teil der ** Funktion des statischen Code-Analyse-Tools "Infer" verwendet, das 2015 als Open Source veröffentlicht wurde **. (BSD-Lizenz ab Februar 2018)
Ab März 2018 gibt es auch Awesome Java, in dem nur sorgfältig ausgewählte Java-Tools aufgelistet sind. Infer wird vorgestellt.
Facebook hat RacerD bereits als CI (Continuous Integration) integriert und seine Android-Version der App hat mehr als 1000 parallele Fehler entdeckt.
Installieren Sie zunächst das statische Code-Analyse-Tool "Infer" (die Version zum Zeitpunkt des Schreibens dieses Artikels ist 0.13.1).
Geben Sie den folgenden Befehl in ein beliebiges Verzeichnis ein.
$ git clone https://github.com/facebook/infer.git
Infer unterstützt nicht nur Java, sondern auch C, C ++, Objective-C (RacerD ist jedoch nur Java). In diesem Artikel werden wir die Docker-Datei neu schreiben, da es ausreicht, über die für die statische Java-Code-Analyse erforderliche Umgebung zu verfügen.
Navigieren Sie zu dem Verzeichnis, das die Dateien für die Installation des Docker-Images enthält.
$ cd infer/docker
Die Verzeichnisstruktur ist wie folgt.
$ tree
.
├── Dockerfile
├── README.md
└── run.sh
Schreiben Sie die Docker-Datei wie folgt neu.
** Unterschied zwischen Dockerfile.origin (vor der Änderung) und Dockerfile (nach der Änderung) **
$ 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
Führen Sie die folgende Shell aus, um das Docker-Image zu installieren.
./run.sh
In der in diesem Artikel verwendeten Umgebung ** Die Installation dauerte ca. 20 Minuten. ** ** **
Erstellen Sie einen Container aus Infers Docker-Image, indem Sie den folgenden Befehl eingeben:
$ docker run -v /home/user/project:/mnt --rm -it infer
Running Infer in Docker · facebook/infer Wiki · GitHub
Wechseln Sie in das folgende gemountete Verzeichnis.
# cd mnt
Erstellen Sie "Hello.java" im Verzeichnis "mnt" auf der Infer-Webseite (Hello, World! | Infer). ..
Sie können den statischen Code von "Hello.java" analysieren, indem Sie den folgenden Befehl eingeben.
# 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
Jetzt möchte ich mit der Analyse des Parallelitätsfehlers beginnen, der das Hauptthema ist.
RacerD führt eine statische Analyse des Java-Codes mit ** "lock" oder "@ThreadSafe annotation" ** durch. Sie können die Annotation-JAR-Datei @ThreadSafe von der JCIP-Website herunterladen (Java Concurrency in Practice).
In diesem Artikel habe ich den folgenden Beispielcode im Verzeichnis "mnt" erstellt.
.
|-- Hello.java
|-- LongContainer.java
|-- UnsafeSequence.java
|-- jcip-annotations.jar
Versuchen Sie zuerst RacerD mit dem auf JCIP veröffentlichten Verstoßcode.
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++;
}
}
Das Problem mit dem obigen Code ist, dass die beiden Threads, die getNext aufrufen, ** den gleichen Wert ** erhalten, wenn das Timing nicht stimmt. Eine Inkrementbeschreibung wie "nextValue ++" sieht aus wie eine Operation, führt jedoch die folgenden drei Operationen aus.
Daher abhängig vom Timing mehrerer Threads Wenn zwei Threads gleichzeitig denselben Wert lesen und auf dieselbe Weise 1 hinzufügen, können die beiden Threads denselben Wert zurückgeben.
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. 64-Bit-Werte atomar lesen und schreiben
Im Java-Speichermodell der Programmiersprache ** wird ein einzelner Schreibvorgang auf einen nichtflüchtigen langen oder doppelten Wert als zwei Schreibvorgänge mit jeweils 32 Bit behandelt. ** Infolgedessen kann der Thread die Kombination der ersten 32 Bits beim Schreiben eines 64-Bit-Werts und der nächsten 32 Bits eines anderen Schreibvorgangs sehen.
Aufgrund dieses Verhaltens können unbestimmte Werte in Code gelesen werden, der für die Thread-Sicherheit erforderlich ist. Daher müssen Multithread-Programme sicherstellen, dass 64-Bit-Werte atomar gelesen und geschrieben werden.
Wenn im folgenden Verstoßcode ein Thread wiederholt die Methode assignValue () und ein anderer Thread wiederholt die Methode printLong () aufruft, verwendet die Methode ** printLong () den Wert i, der weder 0 noch der Wert des Arguments j ist. Kann ausgegeben werden. ** ** **
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
Klassen, die ich für threadsafe verwenden möchte, müssen explizit mit "@ ThreadSafe" versehen werden, aber ich fand, dass RacerDs Analyse von Parallelitätsfehlern sehr effektiv ist.
Recommended Posts