FlowTracker Documentation

repository·master·Indexed 18 days ago

https://github.com/coekie/flowtracker

A Java agent providing deep visibility into data flow within Java programs. FlowTracker tracks how textual and binary data (String, char[], byte[]) is read, manipulated, and written, allowing developers to trace outputs like HTTP responses or database values back to their initial inputs. It uses bytecode instrumentation via the ASM library, method hooking, and ThreadLocals to map data to its origins. The tool includes a web-based UI (default port 8011) for visualizing tracked data and supports snapshots for short-lived processes.

Tokens
5.4K
Snippets
18
Records
25
Agent score
62%

What's inside FlowTracker

  1. Handle short-lived processes with suspendShutdown or snapshotOnExit

    master

    Because FlowTracker runs inside the same JVM as the application, it exits when the application exits. For short-lived processes where you need to inspect the data before the JVM shuts down, use one of these two options:

    1. suspendShutdown: Prevents the JVM from exiting immediately when the application ends, allowing you to inspect the FlowTracker UI.
    2. snapshotOnExit: Automatically writes a ZIP file containing a dump of FlowTracker data and a copy of the UI (HTML/JS) when the JVM exits.

    Note on Snapshots: The HTML in a snapshot must be served via a webserver (e.g., jwebserver) due to browser security restrictions. You can also manually grab snapshots while the app is running from http://localhost:8011/snapshot/minimized or http://localhost:8011/snapshot/full.

    # Example: Using snapshotOnExit with Maven integration tests
    ./mvnw integration-test -Dtest=PetClinicIntegrationTests -DargLine="-javaagent:$FT_JAR=trackCreation;snapshotOnExit=petclinic-snapshot.zip $FT_JVMOPTS"
  2. Handle String literals and interning

    master

    For String literals, FlowTracker creates a new copy of the String and associates its content with a ClassOriginTracker. This is done via StringHook.constantString("value", classId, offset).

    Note on String Interning: This instrumentation breaks the standard JVM guarantee that all identical String constants refer to the same instance. To mitigate side effects, FlowTracker:

    • Uses ConstantDynamic so repeated executions of the same line return the same instance.
    • Rewrites some stringA == stringB expressions to Objects.equals(stringA, stringB).
    • Allows disabling tracking for specific packages (e.g., java.lang.*) via the breakStringInterning configuration setting.
    // Original
    String s = "abc";
    
    // Instrumented
    String s = StringHook.constantString("abc", 1234, 81);
  3. Track values originating from code constants

    master

    FlowTracker treats values coming from the code itself (like primitive or String constants) as origins by using a ClassOriginTracker. This tracker contains a textual representation of the class and its constants.

    When a constant is used, FlowTracker generates a TrackerPoint that points to a specific offset within that class's textual representation. For performance, this is implemented using ConstantDynamic (JEP 309) to ensure the constantPoint lookup happens only once per constant.

    // Original code
    class MyClass {
      void myMethod() {
        char a = 'x';
      }
    }
    
    // Instrumented code (conceptual)
    class MyClass {
      void myMethod() {
        char a = 'x';
        TrackerPoint aTracker = ConstantHook.constantPoint(
          1234 /* id for MyClass*/,
          81 /* offset of 'x' in the ClassOriginTracker content */
        );
      }
    }
  4. Understand the FlowTracker data model

    master

    FlowTracker uses a specific set of abstractions to map data to its origins:

    • Tracker: The central unit of tracking. It holds:
      • content: The actual data (e.g., bytes in an InputStream).
      • source: A mapping that associates specific ranges of the current content to ranges in other trackers (e.g., a String's content pointing back to the FileInputStream it was read from).
    • TrackerRepository: A global registry containing a Map<Object, Tracker> that associates tracked objects with their respective Tracker.
    • TrackerPoint: A pointer to a specific position within a Tracker, representing a single primitive value (like a single byte).
  5. Track primitive values and dataflow

    master

    FlowTracker handles the tracking of primitive values (primarily byte and char, and to a lesser extent int and long) by instrumenting the bytecode to maintain associations between values and their TrackerPoints.

    Because primitives lack identity, FlowTracker cannot use a global map to associate them with trackers. Instead, it rewrites code to store the association in local variables or via specific hooks. For example, when accessing an array element, it uses ArrayHook.getElementTracker to retrieve the tracker and ArrayHook.setElementTracker to propagate it when the value is stored elsewhere.

    byte[] x; byte[] y;
    // ...
    byte b = x[1];
    TrackerPoint bTracker = ArrayHook.getElementTracker(x, 1);
    // ...
    y[2] = b;
    ArrayHook.setElementTracker(y, 2, bTracker);
  6. Understand fallback for untracked values

    master

    FlowTracker does not track every value (e.g., array lengths or complex calculated numerical values) due to performance and complexity constraints.

    When an untracked value enters a tracked context (like being passed as an argument to a tracked method), FlowTracker creates a link to the ClassOriginTracker at the point where the value became tracked, represented by the placeholder "<?>". This allows developers to see that a value (like a String length) originated from a specific line of code, even if the value itself wasn't tracked through its entire lifecycle.

  7. How FlowTracker works internally

    master

    FlowTracker is an instrumenting agent that injects bytecode into classes as they are loaded by the JVM. It focuses on tracking textual and binary data (e.g., String, char[], and byte[]), rather than numerical or structured data.

    Core Mechanisms:

    • Method Hooking: Replaces JDK method calls (like System.arraycopy) with FlowTracker versions or injects code into the end of JDK methods (like FileInputStream.read) to track inputs and outputs.
    • Dataflow Analysis: Performs deeper instrumentation within methods to track local variables and values on the stack.
    • ThreadLocals: Uses ThreadLocals to track method arguments and return values by adding code at the start and end of method invocations.
    • Bytecode Manipulation: Uses the ASM library to perform these injections.
  8. Launch FlowTracker with a Java program

    master

    To use FlowTracker, you must include it as a -javaagent and provide a specific set of JVM options. You can retrieve the required JVM options by running the FlowTracker JAR with the jvmopts command.

    Follow these steps to set up the environment variables and launch your application:

    1. Define the path to the FlowTracker JAR.
    2. Generate the required JVM options using the jvmopts command.
    3. Run your Java application with the -javaagent flag and the generated options.

    By default, FlowTracker starts a webserver on port 8011. You can view the results at http://localhost:8011/.

    FT_JAR=path/to/flowtracker.jar
    FT_JVMOPTS="$(java -jar $FT_JAR jvmopts)"
    
    java -javaagent:$FT_JAR $FT_JVMOPTS
  9. Install and run FlowTracker as a Java agent

    master

    FlowTracker is a Java agent used to track data flow (I/O, file, and network) through a running program.

    Setup Steps:

    1. Download the agent: Get the flowtracker-*.jar from the GitHub releases pages.
    2. Attach the agent: Add the -javaagent flag to your java command line.
    3. Configure JVM options: To ensure accuracy, you must disable certain JVM optimizations. Run the following command to see the required options, and append their output to your java command line:
      java -jar flowtracker.jar jvmopts
    4. Access the UI: By default, FlowTracker starts a webserver on port 8011. Open http://localhost:8011/ in your browser to view the tracked data.
    WARNING

    FlowTracker is currently a proof-of-concept. It introduces significant performance overhead and may not work correctly for all applications.

    java -javaagent:path/to/flowtracker.jar [JVM_OPTS_FROM_JVMOPTS_COMMAND] -jar your-app.jar
  10. Configure FlowTracker options

    master

    FlowTracker is configured via the -javaagent argument using a semicolon-separated list of key=value pairs. For boolean options, you can omit the =value part.

    Syntax: -javaagent:$FT_JAR=option1=value1;option2;option3=value3

    # Example: disabling the webserver and enabling trackCreation
    java -javaagent:$FT_JAR=webserver=false;trackCreation $FT_JVMOPTS
  11. Improve UI visibility by providing source code

    master

    FlowTracker does not require the application's source code to function, but providing it makes the results significantly more readable. If source code is unavailable, FlowTracker uses the Vineflower decompiler to display the code.

    How to provide source code:

    • Maven convention: FlowTracker looks for a [dependency-name]-sources.jar file corresponding to [dependency-name].jar.
    • Manual setup: You can download dependency sources using mvn dependency:sources to ensure the expected JAR files are present in your environment.
  12. Define instrumentation hooks using @Hook annotations

    master

    In FlowTracker, you extend tracking behavior by defining hooks. While HookSpec is the underlying implementation, you primarily interact with it through the @Hook annotation. Each @Hook annotation creates a HookSpec instance that specifies how to inject a call to a 'hook class' at either the start (ON_ENTER) or the end (ON_RETURN) of a target method.

    When defining a hook, you can specify which arguments from the target method should be passed into your hook method using HookArgument constants.