Skip to content

Analysis sessions

Jordan Samhi edited this page Oct 25, 2026 · 4 revisions

Analysis sessions

AndroidAnalysis owns the Soot lifecycle for one APK. By default, opening constructs Android entry points and a call graph synchronously. Opt-in class-only initialization loads the scene for inspection and defers graph construction until needed. Use it for new integrations; use FlowdroidUtils or SootUtils directly only when your application already manages Soot.

This documentation targets AndroSpecter 2.0.0, with JDK 17, Soot 4.7.1, and FlowDroid 2.15.1. Until publication completes, install the repository locally with mvn -B install before building a consumer. Use a readable APK and the Android SDK platforms directory, such as /opt/android-sdk/platforms, containing the required Android platform JARs. See Getting started for project setup.

Open, use, and close on one thread

The following is a complete SessionSummary.java application. Its two arguments are the APK and SDK platforms directory. It collects its own metrics and retains only detached values after closing.

import com.jordansamhi.androspecter.ResultsAccumulator;
import com.jordansamhi.androspecter.analysis.AndroidAnalysis;
import com.jordansamhi.androspecter.analysis.CallGraphAlgorithm;
import com.jordansamhi.androspecter.graph.CallGraphSnapshot;
import java.nio.file.Path;
import java.time.Duration;

/**
 * Command-line example that retains detached graph and metric values after closing a session.
 * The default constructor performs no analysis; invoke {@link #main(String[])} to run it.
 */
public final class SessionSummary {
    /**
     * Opens one APK on the calling thread, collects graph metrics, and prints the detached summary.
     * The caller must provide exclusive access to Soot until the session closes.
     *
     * @param args APK path followed by the Android SDK platforms directory
     * @throws IllegalArgumentException if there are not exactly two arguments or input paths are invalid
     * @throws RuntimeException if backend initialization or graph capture fails
     */
    public static void main(String[] args) {
        if (args.length != 2) {
            throw new IllegalArgumentException("Usage: SessionSummary <apk> <platforms>");
        }
        Path apk = Path.of(args[0]);
        Path platforms = Path.of(args[1]);
        CallGraphSnapshot graph;
        ResultsAccumulator.Snapshot metrics;

        try (var analysis = AndroidAnalysis.builder(apk, platforms)
                .callGraphAlgorithm(CallGraphAlgorithm.CHA)
                .callbackTimeout(Duration.ZERO)
                .open()) {
            graph = analysis.callGraph();
            analysis.metrics().setAppName(apk.getFileName().toString());
            analysis.metrics().setMetric("graphEdges", graph.edges().size());
            analysis.metrics().setMetric("graphIncidentMethods", graph.methods().size());
            metrics = analysis.metrics().snapshot();
            System.out.println("Package: " + analysis.packageName());
        }

        // These values do not refer to the closed Soot scene.
        System.out.println("Analysis: " + graph.analysisId());
        System.out.println(metrics.appName() + ": " + metrics.metrics());
    }
}

AndroidAnalysis.open(apk, platforms) is the shorter form when defaults are sufficient. Building a builder does not initialize Soot; open() does.

Defaults and instance settings

Setting Default Configuration
Call graph algorithm CHA callGraphAlgorithm(CHA/RTA/VTA/SPARK)
Initialization EAGER initializationMode(InitializationMode.CLASSES_ONLY) to defer the graph
Detailed taint paths PRECISE pathReconstruction(PathReconstructionMode.NONE/FAST/PRECISE)
Callback discovery Enabled, default analyzer, limit 100/component, unlimited depth callbacks(CallbackOptions)
Additional dependency classpath Empty additionalClasspath(Path...)
Source/sink definitions Empty sourcesAndSinks(SourceSinkConfiguration)
Callback phase timeout Disabled callbackTimeout(Duration)
Data-flow phase timeout Disabled taintTimeout(Duration)
Path reconstruction timeout Disabled pathTimeout(Duration)
Explicit cancellation None cancellationToken(CancellationToken)
Metrics Independent empty accumulator Populate analysis.metrics() yourself

Positive phase limits round up to whole seconds; zero disables the corresponding limit. These are backend phase limits, not a timeout for the entire session. A callback timeout can leave a partial graph. See Cancellation and logging for deadlines and cooperative cancellation.

The session's immutable sourcesAndSinks() settings remain available after closure. Calling analyzeTaint(otherDefinitions) uses those definitions for that run without replacing the session defaults. No bundled sources or sinks are added implicitly. See Taint analysis.

metrics() does not automatically count classes, methods, or flows. Each session has its own ResultsAccumulator; snapshot() copies its current values for retention or serialization.

Configure analysis and inspect before graph construction

This method-scope snippet requires readable Path apk, Android SDK directory Path platforms, and a readable dependency JAR or class directory Path dependencyJar. No other session or Soot user may be active. Add these imports to the surrounding class:

import com.jordansamhi.androspecter.analysis.AndroidAnalysis;
import com.jordansamhi.androspecter.analysis.CallbackOptions;
import com.jordansamhi.androspecter.analysis.InitializationMode;
import com.jordansamhi.androspecter.analysis.PathReconstructionMode;
import com.jordansamhi.androspecter.inspection.Inspection;
import java.nio.file.Path;
import soot.SootClass;
try (var analysis = AndroidAnalysis.builder(apk, platforms)
        .initializationMode(InitializationMode.CLASSES_ONLY)
        .pathReconstruction(PathReconstructionMode.NONE)
        .callbacks(new CallbackOptions(true, CallbackOptions.Analyzer.DEFAULT, 100, -1))
        .additionalClasspath(dependencyJar)
        .configureFlowDroid(configuration -> configuration.setEnableReflection(true))
        .open()) {
    analysis.inspect(Inspection.builder()
            .classes(SootClass::isApplicationClass)
            .onClass(clazz -> System.out.println(clazz.getName()))
            .build());
    // Android callback discovery and graph construction happen here for the first time.
    var graph = analysis.callGraph();
    System.out.println(graph.edges().size());
}

CLASSES_ONLY performs class loading and Jimple/framework-model preparation, but skips callback discovery and graph construction at open. Inspection and transformations can operate immediately. The first callGraph() builds the Android model in the same Soot generation; analyzeTaint(...) also constructs its prerequisites if needed. Promotion does not reset the scene, so body edits survive it. The normal caveats about explicit backend optimizations and transformations still apply. Class-only mode remains a single managed session with the same owning-thread and cleanup rules; it is not a second concurrent-analysis mechanism.

CallbackOptions(enabled, analyzer, maxCallbacksPerComponent, maxAnalysisDepth) is immutable. Analyzer is DEFAULT or FAST; defaults() returns (true, DEFAULT, 100, -1). For DEFAULT, a callback limit of zero is unlimited; exceeding a positive limit causes FlowDroid to omit that component's callbacks. A depth of -1 or zero is unlimited. FlowDroid 2.15.1's FAST analyzer ignores these callback-count/depth limits and the separately configured callback timeout. Negative callback limits and depths below -1 throw IllegalArgumentException; a null analyzer is rejected. Disabling or limiting discovery may omit Android behavior from graph and taint results.

PathReconstructionMode.NONE requests detected flows without detailed reconstructed paths; FAST and PRECISE select the corresponding FlowDroid strategies. Check each finding's pathAvailable() flag instead of assuming a path exists from the setting alone.

additionalClasspath(...) replaces previous entries, copies and normalizes paths, and deduplicates them in encounter order. Entries are appended after the selected Android platform JAR. They provide dependencies, not extra APKs to analyze. Readability is checked at open; wildcards, empty classpath segments, and paths containing the platform classpath separator are unsupported. Invalid entries throw IllegalArgumentException.

Advanced FlowDroid configuration

configureFlowDroid(Consumer<InfoflowAndroidConfiguration>) runs synchronously after typed settings and before backend construction or class loading. Later calls replace the previous customizer. Ordinary options set there can override typed settings.

The callback receives a configuration copy. Instance settings are copied back, including independent mutable category keys; retaining that object and changing it later does not change the active backend configuration. Copying is audited against FlowDroid 2.15.1, including settings omitted by upstream merge.

The callback must not change the managed APK, platforms directory, source/sink file, or Soot integration mode, and must not disable taint execution. Such changes throw IllegalArgumentException. Changes to FlowDroid's global static configuration are unsupported, rejected, and restored when detected. Calling G.reset() is detected and throws IllegalStateException. The hook is trusted application code, not a sandbox: other side effects are prohibited by contract and cannot be rolled back. Additional classpath entries are validated after customization as well.

A failed customization prevents the session from opening and releases acquired ownership. Configure the existing sourcesAndSinks(...) API to select definitions; the advanced hook is not an alternative input-file loader.

Ownership and lifetime

  • Open, inspect, analyze, transform, and close a session on the thread that created it. Use try-with-resources even when a callback or backend operation may throw.
  • Only one managed AndroidAnalysis can be open per JVM. The guard rejects another managed session; it does not coordinate arbitrary external Soot code. For simultaneous independent APK analyses, use separate JVM processes.
  • Opening resets global Soot state. Closing resets the session's Soot generation and releases ownership. If external code already reset Soot, later live operations fail and close avoids resetting that externally created generation.
  • SootClass, SootMethod, Unit, invocation expressions, and other callback values are borrowed live objects. Do not retain or use them after close or G.reset(). Copy names, signatures, or other required values while the session is valid.
  • Graph snapshots, taint result records, and metric snapshots contain detached immutable values and can outlive the session. The session ID associates graph and taint results with their session; it does not describe a particular transformation revision.

The managed guard does not make Soot thread-safe. Do not call G.reset(), another initializer, or unrelated scene-mutating code while a session is in use.

Operations and graph refresh

Operation Behavior
callGraph() Builds the graph if deferred, then returns an indexed immutable snapshot cached until invalidated
inspect(Inspection) Runs synchronous read-oriented callbacks against live Soot objects
apply(Transformation) Runs a mutation and invalidates the cached graph before starting
analyzeTaint() Uses the session's configured definitions and returns detached results
analyzeTaint(definitions) Uses explicit definitions for this run and returns detached results

After a transformation or taint run, the next callGraph() rebuilds the graph before taking a snapshot. Graph-construction failures propagate as exceptions; a failed rebuild is not cached as a successful snapshot. Already returned snapshots remain unchanged. Transformations are not transactional: if one fails after changing a body, the changes are not rolled back, but the cached graph is still invalidated. Mutations made directly through borrowed Soot objects or inspection callbacks are not automatically tracked; use apply(...) for them. See Call graphs and inspection and Instrumentation.

Borrow an existing scene

AnalysisContext.capture() is for applications that already initialized and own Soot. It records the current Soot generation and thread but does not load an APK or acquire the managed-session guard. Closing this borrowed context invalidates the view without resetting Soot. checkValid() checks thread ownership, closure, reset, and cancellation; isValid() checks lifecycle validity without cancellation.

When obtaining a context through analysis.context(), close the AndroidAnalysis itself. Closing only its context does not release the managed session.

Source: AndroidAnalysis, AnalysisContext.

Clone this wiki locally