Skip to content

[Unity][Android] AppCrash handler performs synchronous JNI tracking during crash handling and causes a secondary AndroidJavaException #10

Description

@garyliu0816

Summary

When C# exception auto-tracking is enabled with TDAutoTrackEventType.AppCrash, TDExceptionHandler._LogHandler synchronously calls TDAnalytics.Track("ta_app_crash", ...) from Unity's global log callback.

If the Error log is emitted while IL2CPP/HybridCLR is already handling a native crash, this performs an Android JNI call in an unstable crash context. The JNI call can throw a secondary AndroidJavaException; formatting/printing that Java exception then reports java.lang.StackOverflowError thrown while calling printStackTrace, obscuring or amplifying the original crash.

This issue is intermittent because it requires the Unity Error callback and an unhealthy JNI/native runtime state to overlap.

Environment

  • Unity: 2022.3.62f2
  • Platform: Android 12, arm64
  • Backend: IL2CPP + HybridCLR 8.3.0
  • Device observed: OPPO PFVM10
  • ThinkingData Unity SDK: reproduced with 3.4.8
  • Latest checked version: 3.5.0

Relevant SDK code

In v3.5.0, the handler still reports synchronously:

private static void _LogHandler(string logString, string stackTrace, LogType type)
{
    if (type == LogType.Error || type == LogType.Exception || type == LogType.Assert)
    {
        // Build properties...
        TDAnalytics.Track("ta_app_crash", properties);
    }
}

Source:
https://github.com/ThinkingDataAnalytics/unity-sdk/blob/v3.5.0/ThinkingAnalytics/Exception/TDException.cs#L80-L96

The Android wrapper catches the JNI exception but logs the complete exception object:

catch (Exception e)
{
    TDLog.w("ThinkingAnalytics: unexpected exception: " + e);
}

Source:
https://github.com/ThinkingDataAnalytics/unity-sdk/blob/v3.5.0/ThinkingAnalytics/Wrapper/TDAndroidWrapper.cs#L133-L143

Sanitized log excerpt

[ThinkingData] Warning: ThinkingAnalytics: unexpected exception:
UnityEngine.AndroidJavaException
  at UnityEngine._AndroidJNIHelper.GetMethodID(...)
  at UnityEngine.AndroidJavaObject._CallStatic(...)
  at ThinkingData.Analytics.Wrapper.TDWrapper.track(...)
  at ThinkingData.Analytics.TDException.TDExceptionHandler._LogHandler(...)
  at UnityEngine.Application.CallLogCallback(...)

JNI WARNING: java.lang.StackOverflowError thrown while calling printStackTrace
[hybridclr] ========== CRASH!!! IL2CPP+HYBRIDCLR STACK TRACE ==================
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Cause: null pointer dereference

Normal analytics events, including events immediately before this crash, were successfully queued and uploaded. Therefore this does not appear to be caused by late ThinkingData initialization.

The product-fetch failure occurring earlier was logged as LogType.Warning, while _LogHandler only processes Error/Exception/Assert, so that warning alone does not invoke the crash handler.

Expected behavior

The crash/error collector should not synchronously perform JNI/native analytics work from Application.logMessageReceived or AppDomain.UnhandledException, especially during a fatal native crash.

Actual behavior

The exception handler immediately calls the Android bridge. A secondary JNI exception is produced while the runtime is already failing, and its stack printing can produce an additional StackOverflowError warning before the final SIGSEGV.

Suggested fix

Please consider:

  1. Add a thread-safe reentrancy guard to the exception handler.
  2. Only copy/enqueue crash information inside the Unity log callback; upload it later from a safe update loop.
  3. Persist fatal-crash information locally and upload it on the next launch instead of calling JNI during crash handling.
  4. Avoid logging or formatting the complete AndroidJavaException from within the crash-reporting path.
  5. Filter SDK-generated logs from C# exception auto-tracking.

Current workaround

Setting:

TDPublicConfig.DisableCSharpException = true;

avoids this C# callback path, but it also disables Unity C# exception auto-collection. We would prefer to keep exception collection enabled with a crash-safe reporting implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions