didi/DoKit · error · UnsupportedOperationException

leakTraceAsFakeException() can only be called when leakFound

Error message

leakTraceAsFakeException() can only be called when leakFound is true

What it means

Thrown by AnalysisResult.leakTraceAsFakeException() when it is invoked on an AnalysisResult whose leakFound field is false. The method fabricates a RuntimeException whose stack trace mirrors the leak trace (leaking class <- holder chain); that only makes sense when analysis actually found a leak. It is a guard against calling a result accessor on a non-leak result.

Source

Thrown at Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/AnalysisResult.java:110

   * <pre>
   * * com.foo.WibbleActivity has leaked:
   * * GC ROOT static com.foo.Bar.qux
   * * references com.foo.Quz.context
   * * leaks com.foo.WibbleActivity instance
   * </pre>
   *
   * <p>Will turn into an exception with the following stacktrace:
   * <pre>
   * java.lang.RuntimeException: com.foo.WibbleActivity leak from com.foo.Bar (holder=CLASS,
   * type=STATIC_FIELD)
   *         at com.foo.Bar.qux(Bar.java:42)
   *         at com.foo.Quz.context(Quz.java:42)
   *         at com.foo.WibbleActivity.leaking(WibbleActivity.java:42)
   * </pre>
   */
  public @NonNull RuntimeException leakTraceAsFakeException() {
    if (!leakFound) {
      throw new UnsupportedOperationException(
          "leakTraceAsFakeException() can only be called when leakFound is true");
    }
    LeakTraceElement firstElement = leakTrace.elements.get(0);
    String rootSimpleName = classSimpleName(firstElement.className);
    String leakSimpleName = classSimpleName(className);

    String exceptionMessage = leakSimpleName
        + " leak from "
        + rootSimpleName
        + " (holder="
        + firstElement.holder
        + ", type="
        + firstElement.type
        + ")";
    RuntimeException exception = new RuntimeException(exceptionMessage);

    StackTraceElement[] stackTrace = new StackTraceElement[leakTrace.elements.size()];
    int i = 0;

View on GitHub (pinned to 626827cddb)

Solutions

  1. Check result.leakFound before calling leakTraceAsFakeException()
  2. Branch on the full result state: leakFound, failureText (analysis failed) before touching leakTrace
  3. If you need the leak trace outside the exception, access result.leakTrace directly (also only when leakFound is true)

Example fix

// before
RuntimeException fake = result.leakTraceAsFakeException();

// after
if (result.leakFound) {
  RuntimeException fake = result.leakTraceAsFakeException();
  crashReporter.report(fake);
} else if (result.failureText != null) {
  Log.e("LeakCanary", result.failureText);
}
Defensive patterns

Strategy: validation

Validate before calling

if (result.leakFound) { RuntimeException fake = result.leakTraceAsFakeException(); }

Prevention

When it happens

Trigger: Calling analysisResult.leakTraceAsFakeException() without first checking analysisResult.leakFound. Typical in code that unconditionally converts every analysis result into an exception for crash reporting or test assertions, including results that reported NO_LEAK or failed analysis.

Common situations: Integrating LeakCanary results into a crash reporter and forgetting the leakFound branch. Writing unit tests over AnalysisResult that exercise the fake-exception path for all results. Handling AnalysisResult.failureText / retention-visibility results where leakFound is false.

Related errors


AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14). Data as JSON: /api/errors/8310e89c76511181. Report an issue: GitHub.