Ich habe das Java-Parallelitäts-Fehleranalyse-Tool "Racer D" von Facebook ausprobiert.

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)

Was ist "Racer D"?

"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 Infer

Installieren Sie zunächst das statische Code-Analyse-Tool "Infer" (die Version zum Zeitpunkt des Schreibens dieses Artikels ist 0.13.1).

Klonen Sie das Git-Repository

Geben Sie den folgenden Befehl in ein beliebiges Verzeichnis ein.

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

Dockerfile neu schreiben

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

Installieren Sie das Docker-Image

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. ** ** **

Überprüfen Sie die Funktion von Infer

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

Versuchen Sie RacerD mit Code für Parallelitätsfehler

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

Beispiel 1. Eine nicht threadsichere Sequenzgenerierungsmethode

Versuchen Sie zuerst RacerD mit dem auf JCIP veröffentlichten Verstoßcode.

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.

  1. Lesen Sie den Wert
  2. Fügen Sie 1 hinzu
  3. Schreiben Sie den neuen Wert auf

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.

Analyseergebnis: "THREAD_SAFETY_VIOLATION" erkannt

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

Beispiel 2. Lesen und Schreiben von 64-Bit-Werten

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.

Verstoßcode

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);
  }
}

Analyseergebnis: "THREAD_SAFETY_VIOLATION" erkannt

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

Zusammenfassung

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

Ich habe das Java-Parallelitäts-Fehleranalyse-Tool "Racer D" von Facebook ausprobiert.
Ich habe Drools (Java, InputStream) ausprobiert.
Ich habe versucht, Java REPL zu verwenden
Ich habe versucht, den Quellcode zu analysieren
Bei der Java-Parallelverarbeitung bin ich mir nicht sicher
Ich habe versucht, Metaprogrammierung mit Java
Ich habe eine morphologische Analyse mit MeCab versucht
Ich habe versucht, mit Java zu interagieren
Ich habe versucht, UDP mit Java zu kommunizieren
Ich habe versucht, die Java8 Stream API zu verwenden
Ich habe Javas Micro-Benchmarking-Tool JMH ausprobiert
Ich habe versucht, JWT in Java zu verwenden
Ich habe jetzt versucht, Java 8 zusammenzufassen
Ich habe versucht, Java Memo LocalDate zu verwenden
Ich habe versucht, Google HttpClient von Java zu verwenden
Ich habe versucht, die Elasticsearch-API in Java zu verwenden
Ich habe versucht, das Java-Diagnosetool Arthas zu verwenden
Ich habe Cassandras Object Mapper für Java ausprobiert
Ich habe versucht, Java-Lambda-Ausdrücke zusammenzufassen
Ich habe das neue Yuan-Problem in Java ausprobiert
Ich habe versucht, OpenCV mit Java + Tomcat zu verwenden
Ich habe Googles Aufnahmeprüfung (inoffiziell) [Java] ausprobiert.