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