Skip to content

Conversation

@dkimitsa
Copy link
Contributor

@dkimitsa dkimitsa commented Sep 8, 2025

Reverts #754

Reasons to revert:

  1. While these changes allowed to work-around breakpoints that were not worked, this introduces another major issue that breaks class initialisation and cause java.lang.NoClassDefFoundError: Could not initialize class.

  2. Another moment to revert: Jetbrains has fixed https://youtrack.jetbrains.com/issue/IDEA-332794 and delivered fix in 2025.2. (bad moment -- it fixes big Java case but there is still issue with Robo)

  3. these changes were applied only to NSObject subclasses but any inner Java classes affected.

While 1 is important that becomes sometimes as blocker its better to roll back and look into debugger issues separately (It looks like Jetbrains changes things in debugger and parts there were working stopped working now).

Minimal code to reproduce:

package com.example.mobile.ios;

import org.robovm.apple.foundation.NSObject;
import org.robovm.apple.foundation.NSString;

public class Main {
    public static void main(String[] args) throws Exception {
        new NSObject(); // cause Failed to preload inner ObjCClass host class com.example.mobile.ios.Foo: null
        new Foo();      // cause java.lang.NoClassDefFoundError: Could not initialize class com.example.mobile.ios.Foo
        new Foo.FooInner(); // to keep class from being tree shaked
    }
}

class Foo extends NSObject {
    static NSString npeHere = new NSString("NPE is here");

    public static class FooInner extends NSObject {
        public static void foo() { Foo.npeHere.toString(); }
    }
}

Workaround for existing/release versions

add following code at early iOS initialization code:

Class.forName("org.robovm.objc.ObjCObject"); // FIXME: workaround for Failed to preload inner ObjCClass xxxx

Root case:

Class marked as Broken due to exception happens during class initialisation:

Throwing a java/lang/NullPointerException. Call stack:
    org/robovm/apple/foundation/NSObject.alloc(Lorg/robovm/objc/ObjCClass;)J:185
    org/robovm/apple/foundation/NSObject.alloc()J:179
    org/robovm/objc/ObjCObject.<init>()V:86
    org/robovm/apple/foundation/NSObject.<init>(Lorg/robovm/apple/foundation/NSObject$SkipInit;)V:139
    org/robovm/apple/foundation/NSString.<init>(Ljava/lang/String;)V:108
    com/example/mobile/ios/Foo.<clinit>()V:27
    java/lang/Class.classForName(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;:-2
    java/lang/Class.forName(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;:218
    java/lang/Class.forName(Ljava/lang/String;)Ljava/lang/Class;:176
    org/robovm/objc/ObjCClass.<clinit>()V:78
    org/robovm/objc/ObjCObject.<clinit>()V:77
    com/example/mobile/ios/Main.testNSObject()V:9
    com/example/mobile/ios/Main.main([Ljava/lang/String;)V:14

and alloc fails as alloc property of NSObject is null:

private static final Selector alloc = Selector.register("alloc");
    static long alloc(ObjCClass c) {
        long h = ObjCRuntime.ptr_objc_msgSend(c.getHandle(), alloc.getHandle());
        if (h == 0L) {
            throw new OutOfMemoryError();
        }
        return h;
    }

this is happens as NSObject didn't finish own class initialization as called for super ObjCObject.<clinit>.
it started - preloading collected classes (funcionality of #754) and com/example/mobile/ios/Foo.<clinit> was creating NSString() that touched not-initialized NSObject.alloc property causing NPE and marking Foo as corrupted.

Workaround Class.forName("org.robovm.objc.ObjCObject") makes ObjCClass being initialised outside of any other code that might cause cyclical dependency.

@Tom-Ski Tom-Ski merged commit 3f6e121 into MobiVM:master Sep 28, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants