1

I have received the following crash report

java.lang.ArrayStoreException: de.benibela.videlibri.jni.Bridge$Account cannot be stored in an array of type de.benibela.videlibri.jni.Bridge$Account[]
 at de.benibela.videlibri.jni.Bridge.VLGetAccounts(Native Method)
 at de.benibela.videlibri.Accounts.refreshAll(Accounts.kt:61)
 at de.benibela.videlibri.VideLibriApp$Companion.initializeAll(VideLibriApp.kt:305)
 at de.benibela.videlibri.VideLibriApp.onCreate(VideLibriApp.kt:49)
 at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1020)
 at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5009)

from a Samsung SM-G900F Android 5.0 phone.

Is this error not impossible?

I have this Java code

package de.benibela.videlibri.jni;
public class Bridge { 
  //...
  public static class Account implements Serializable{
    @NotNull public String libId, name, pass, prettyName;
    public int type;
    public boolean extend;
    public int extendDays;
    public boolean history;
    public int lastCheckDate;
    public Account () {
        libId = name = pass = prettyName = "";
    }
    public Account (@NotNull String libId, @NotNull String name, @NotNull String pass, @NotNull String prettyName,
             int type, boolean extend,
             int extendDays, boolean history,
             int lastCheckDate) {
        this.libId = libId;
        this.name = name;
        this.pass = pass;
        this.prettyName = prettyName;
        this.type = type;
        this.extend = extend;
        this.extendDays = extendDays;
        this.history = history;
        this.lastCheckDate = lastCheckDate;
    }
    //...

and this Pascal code to interact with it:

//global variables
var accountClass: jobject;
    accountClassInitWithData: jmethodID;
    accounts: TAccountList;


//...
with j do begin
  accountClass := newGlobalRefAndDelete(getclass('de/benibela/videlibri/jni/Bridge$Account'));
  accountClassInitWithData := getmethod(accountClass, '<init>', '(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZIZI)V');
end;

function accountToJAccount(account: TCustomAccountAccess): jobject;
var args: array[0..8] of jvalue;
  i: Integer;
begin
  with j  do begin
    args[0].l := stringToJString(account.getLibrary().id);
    args[1].l := stringToJString(account.getUser());
    args[2].l := stringToJString(account.passWord);
    args[3].l := stringToJString(account.prettyName);
    args[4].i := account.accountType;
    args[5].z := booleanToJboolean(account.extendType <> etNever);
    args[6].i := account.extendDays;
    args[7].z := booleanToJboolean(account.keepHistory);
    args[8].i := account.lastCheckDate;
    result := newObject(accountClass, accountClassInitWithData, @args[0]);
    for i := 0 to 3 do deleteLocalRef(args[i].l);
  end;
end;            
function Java_de_benibela_VideLibri_Bridge_VLGetAccounts(env:PJNIEnv; this:jobject): jobject; cdecl;
var
  i: Integer;
begin
  if logging then bbdebugtools.log('de.benibela.VideLibri.Bride.VLGetAccounts (started)');
  try
    result := j.newObjectArray(accounts.Count, accountClass, nil);
    with j do
      for i := 0 to accounts.Count - 1 do
        setObjectArrayElementAndDelete(result, i,  accountToJAccount(accounts[i]));
  except
    on e: Exception do throwExceptionToJava(e);
  end;
  if logging then bbdebugtools.log('de.benibela.VideLibri.Bride.VLGetAccounts (ended)');
end;

j is just a simple wrapper around the JNI env pointer:

type TJavaEnv = record
  env: PJNIEnv;
  //...
end;
threadvar j: TJavaEnv; //JNI helper object

function TJavaEnv.newObject(c: jclass; m: jmethodID; args: Pjvalue): jobject;
begin
  result := env^^.NewObjectA(env, c, m, args);
end;
function TJavaEnv.newObjectArray(len: integer; c: jclass; def: jobject): jobject;
begin
  result := env^^.NewObjectArray(env,   len, c, def);
end;
function TJavaEnv.newGlobalRefAndDelete(obj: jobject): jobject;
begin
  result := env^^.NewGlobalRef(env, obj);
  deleteLocalRef(obj);
end;
function TJavaEnv.getclass(n: pchar): jclass;
var
  jn: jobject;
begin
  if jCustomClassLoader <> nil then begin
    jn := stringToJString(n);
    result := callObjectMethod(jCustomClassLoader, jCustomClassLoaderFindClassMethod, @jn);
    if ExceptionCheck then begin
      env^^.ExceptionClear(env);
      result := nil;
    end;
    deleteLocalRef(jn);
    if result <> nil then exit;
  end;
  result := env^^.FindClass(env, n);
  checkResult(result, 'class', n, '');
end;
function TJavaEnv.getmethod(c: jclass; n, sig: pchar): jmethodID;
begin
  result := env^^.GetMethodID(env, c, n, sig);
  checkResult(result, 'method', n, '');
end;
function TJavaEnv.ExceptionCheck: boolean;
begin
  result := env^^.ExceptionCheck(env) <> JNI_FALSE
end;
function TJavaEnv.callObjectMethod(obj: jobject; methodID: jmethodID; args: Pjvalue): jobject;
begin
  result := env^^.CallObjectMethodA(env, obj, methodID, args);
end;
procedure TJavaEnv.setObjectArrayElement(a: jobject; index: integer; v: jobject);
begin
  env^^.SetObjectArrayElement(env, a, index, v);
end;
procedure TJavaEnv.setObjectArrayElementAndDelete(a: jobject; index: integer; v: jobject);
begin
  setObjectArrayElement(a, index, v);
  deleteLocalRef(v);
end;
procedure TJavaEnv.deleteLocalRef(obj: jobject);
begin
  env^^.DeleteLocalRef(env, obj);
end;
function TJavaEnv.NewStringUTF(s: string): jobject; inline;
begin
  result := env^^.NewStringUTF(env, pchar(s));
end;
function TJavaEnv.stringToJString(s: string; conversionMode: TStringConversionMode = scmConvertAndRepairUTF8ToMUTF8): jobject;
var ok: integer;
begin
  if (conversionMode = scmAssumeMUTF8) then
    exit(NewStringUTF(s));
  ok := isValidModifiedUTF8(s, conversionMode);
  if ok = 0 then
    exit(NewStringUTF(s));
  if (ok = INVALID_UTF8) and (conversionMode = scmConvertValidUTF8ToMUTF8) then
    raise EAndroidInterfaceException.create('String is invalid utf-8: '+strFromPtr(pointer(s))+':'+inttostr(length(s)));
  exit(NewStringUTF(repairModifiedUTF8(s)));
end;
function TJavaEnv.booleanToJboolean(b: boolean): jboolean;
begin
  if b then result := JNI_TRUE
  else result := JNI_FALSE;
end;
procedure setCustomClassLoaderFromLoadedClass(c: jclass);
var classClass, classLoaderClass: jclass;
  getClassLoaderMethod: jmethodID;
begin
  //see https://stackoverflow.com/questions/13263340/findclass-from-any-thread-in-android-jni
  with needJ do begin
    classClass := env^^.GetObjectClass(env, c);
    getClassLoaderMethod := getmethod(classClass, 'getClassLoader', '()Ljava/lang/ClassLoader;');
    jCustomClassLoader := newGlobalRefAndDelete(callObjectMethodChecked(c, getClassLoaderMethod));

    classLoaderClass := env^^.FindClass(env, 'java/lang/ClassLoader');
    jCustomClassLoaderFindClassMethod := getmethod(classLoaderClass, 'findClass', '(Ljava/lang/String;)Ljava/lang/Class;');
  end;
end;
//in JNI_OnLoad
  setCustomClassLoaderFromLoadedClass(j.env^^.FindClass(j.env, 'de/benibela/videlibri/jni/Bridge'));
BeniBela
  • 16,412
  • 4
  • 45
  • 52
  • This error is very possible: it can mean the classes have different classloaders and so are different objects. – Alexey Romanov Jan 29 '19 at 07:43
  • But when I write `newObjectArray(accounts.Count, accountClass, nil)` and `newObject(accountClass, ...)` that is the same class reference `accountClass`. Unless the reference gets lost somehow – BeniBela Jan 29 '19 at 12:11
  • I'm seeing this error happen on Samsung devices on Android 5 for my own app. During the initialisation of an enum of all things! `MyEnum cannot be stored in an array of type MyEnum[]` . Did you happen to find a solution? – kassim Mar 28 '19 at 10:46
  • @kassim I did not – BeniBela Apr 06 '19 at 11:17
  • so @AlexeyRomanov's suggestion of multiple classloaders had me thinking that perhaps our modularization of our app was causing it, which, apparently it was (we're using feature modules and the enum was in one of these modules, though only used by code in that module), after we moved our enum into our base module we didn't see the crash again - I'm not sure if that piece of info might help you – kassim Apr 09 '19 at 12:46

0 Answers0