I have a flutter app that generates a lot of hidden API warnings:
Accessing hidden method Landroid/view/accessibility/AccessibilityNodeInfo;->getSourceNodeId()J (greylist,test-api, reflection, allowed)
Accessing hidden method Landroid/view/accessibility/AccessibilityRecord;->getSourceNodeId()J (greylist, reflection, allowed)
Accessing hidden field Landroid/view/accessibility/AccessibilityNodeInfo;->mChildNodeIds:Landroid/util/LongArray; (greylist, reflection, allowed)
Accessing hidden method Landroid/util/LongArray;->get(I)J (greylist, reflection, allowed)
...
Accessing hidden method Landroid/database/sqlite/SQLiteDatabase;-><clinit>()V (blacklist, linking, denied)
Accessing hidden field Landroid/database/sqlite/SQLiteDatabase;->DEBUG_CLOSE_IDLE_CONNECTIONS:Z (greylist-max-o, linking, denied)
Accessing hidden field Landroid/database/sqlite/SQLiteDatabase;->sActiveDatabases:Ljava/util/WeakHashMap; (greylist-max-o, linking, denied)
Accessing hidden field Landroid/database/sqlite/SQLiteDatabase;->CONFLICT_VALUES:[Ljava/lang/String; (greylist, linking, allowed)
And so on. I haven't written any code to use Android's SQLiteDatabase
or AccessibilityNodeInfo
, but this doesn't provide any information about which bit of code is calling these hidden methods. It could be in Flutter, or one of the several plugins I have. Short of mutilating my code to remove these plugins, is there anything I can do to find out what code is calling these? I.e. to get a stack trace.
Does Android have an option to crash on hidden field accesses rather than just logging them, or something similar?
Aha, thanks to Mark Keen's link, I was able to create a custom Application
for my Flutter app that logs the stack track for strict mode violations. Create a MainApplication.kt
Kotlin file like this:
package com.yourdomain.flutter_app
import android.app.Application
import android.os.StrictMode
import android.os.StrictMode.OnVmViolationListener
import android.os.StrictMode.VmPolicy
import android.os.strictmode.Violation
import android.util.Log
import io.flutter.view.FlutterMain
import java.io.PrintWriter
import java.io.StringWriter
import java.util.concurrent.Executor
class CurrentThreadExecutor : Executor {
override fun execute(r: Runnable) {
r.run()
}
}
class StacktraceLogger : OnVmViolationListener {
override fun onVmViolation(v: Violation) {
val sw = StringWriter()
val pw = PrintWriter(sw)
v.printStackTrace(pw)
Log.e("STRICTMODE", sw.toString())
}
}
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
val policy = VmPolicy.Builder()
.detectAll()
.detectNonSdkApiUsage()
.penaltyListener(CurrentThreadExecutor(), StacktraceLogger())
.build()
StrictMode.setVmPolicy(policy)
FlutterMain.startInitialization(applicationContext)
}
}
And then point your AndroidManifest.xml
to it:
<application
android:name="com.yourdomain.flutter_app.MainApplication"
Then when you run it you should see a stack trace. In this case the AccessibilityNodeInfo
stuff does appear to be a Flutter issue:
2021-02-20 12:43:11.986 3291-3291/uk.co.timhutt.flutter_app E/STRICTMODE: android.os.strictmode.NonSdkApiUsedViolation: Landroid/view/accessibility/AccessibilityNodeInfo;->getSourceNodeId()J
at android.os.StrictMode.lambda$static$1(StrictMode.java:416)
at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2)
at java.lang.Class.getDeclaredMethodInternal(Native Method)
at java.lang.Class.getPublicMethodRecursive(Class.java:2079)
at java.lang.Class.getMethod(Class.java:2066)
at java.lang.Class.getMethod(Class.java:1693)
at io.flutter.view.AccessibilityViewEmbedder$ReflectionAccessors.<init>(AccessibilityViewEmbedder.java:446)
at io.flutter.view.AccessibilityViewEmbedder$ReflectionAccessors.<init>(AccessibilityViewEmbedder.java:429)
at io.flutter.view.AccessibilityViewEmbedder.<init>(AccessibilityViewEmbedder.java:70)
at io.flutter.view.AccessibilityBridge.<init>(AccessibilityBridge.java:346)
at io.flutter.embedding.android.FlutterView.attachToFlutterEngine(FlutterView.java:919)
at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onCreateView(FlutterActivityAndFragmentDelegate.java:294)
at io.flutter.embedding.android.FlutterActivity.createFlutterView(FlutterActivity.java:520)
at io.flutter.embedding.android.FlutterActivity.onCreate(FlutterActivity.java:414)
at android.app.Activity.performCreate(Activity.java:8000)
at android.app.Activity.performCreate(Activity.java:7984)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3404)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7660)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Weirdly, it still didn't print any stack traces for the SQLite errors, but I noticed that the first errors were for "reflection" whereas those were for "linking". I am linking with a Rust library that uses a bundled copy of SQLite - using this dependency:
rusqlite = { version = "0.24.2", features = ["bundled"] }
And then loading it in Dart:
final DynamicLibrary mapRenderNative = Platform.isAndroid
? DynamicLibrary.open("libmap_render.so")
: DynamicLibrary.process();
When I commented that line out and ran wgradle clean
(necessary it seems), the errors went away! Wtf? It seems like Android intercepts dlopen()
and checks if you link against any of the forbidden symbols.
But the really weird thing is that my libmap_render.so
doesn't link with SQLite
(because it's statically linked into it):
>llvm-readelf -d target\aarch64-linux-android\debug\libmap_render.so
Dynamic section at offset 0x52768 contains 23 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libdl.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so]
0x000000000000001e (FLAGS) BIND_NOW
0x000000006ffffffb (FLAGS_1) NOW
0x0000000000000007 (RELA) 0x988
0x0000000000000008 (RELASZ) 9768 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffff9 (RELACOUNT) 405
0x0000000000000017 (JMPREL) 0x2fb0
0x0000000000000002 (PLTRELSZ) 888 (bytes)
0x0000000000000003 (PLTGOT) 0x54940
0x0000000000000014 (PLTREL) RELA
0x0000000000000006 (SYMTAB) 0x308
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000005 (STRTAB) 0x79c
0x000000000000000a (STRSZ) 491 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x778
0x000000000000001a (FINI_ARRAY) 0x54758
0x000000000000001c (FINI_ARRAYSZ) 16 (bytes)
0x000000006ffffff0 (VERSYM) 0x6e0
0x000000006ffffffe (VERNEED) 0x734
0x000000006fffffff (VERNEEDNUM) 2
0x0000000000000000 (NULL) 0x0
I also couldn't find any SQLite symbols in the symbol table, relocations, etc. So I have literally no idea what Android is doing. Does it literally scan the binary?
I'll open a new question for that I think.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With