Skip to content

Commit

Permalink
* workaround: idea debugger doesn't stop in Inner classes that extend…
Browse files Browse the repository at this point in the history
…s from ObjCObject (#754)

## Root case:
ObjCClass preloads all instances of ObjCObject to find out if these have NativeObject annotations. As result inner class might be loaded before hosting class. This makes Idea debugger not happy and it ignores CLASS_PREPARE event and doesn't apply breakpoints to it.
https://youtrack.jetbrains.com/issue/IDEA-332794

## Workaround
Lets preload host classes before all ObjCObject classes.

NB: affects only debug builds
  • Loading branch information
dkimitsa authored Oct 8, 2023
1 parent 6e31b4c commit f8a81f5
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.robovm.compiler.plugin.AbstractCompilerPlugin;
import org.robovm.compiler.plugin.CompilerPlugin;
import org.robovm.compiler.target.framework.FrameworkTarget;
import org.robovm.compiler.util.generic.SootClassUtils;
import org.robovm.compiler.util.generic.SootMethodType;
import soot.Body;
import soot.BooleanType;
Expand Down Expand Up @@ -87,6 +88,7 @@
import soot.tagkit.SignatureTag;
import soot.util.Chain;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -694,6 +696,7 @@ public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder)
@Override
public void beforeLinker(Config config, Linker linker, Set<Clazz> classes) {
preloadClassesForFramework(config, linker, classes);
preloadObjCClassHosts(config, linker, classes);
}

private static <E> List<E> l(E head, List<E> tail) {
Expand Down Expand Up @@ -2029,6 +2032,49 @@ private void preloadClassesForFramework(Config config, Linker linker, Set<Clazz>
}
}

private void preloadObjCClassHosts(Config config, Linker linker, Set<Clazz> classes) {
// TODO: remove once resolved on Idea side
// affects only debug builds
// workaround for Idea Bug: https://youtrack.jetbrains.com/issue/IDEA-332794
// Idea debugger is not happy if inner class is being loaded before host ones and
// ignores breakpoints inside.
// preload if all ObjCClass's is happening in ObjCClass.java, static initializer
// workaround -- is to preload all hosts before loading ObjCClass
if (config.isDebug()) {
SootClass objCObjectClazz = classes.stream()
.filter( c -> c.getClassName().equals(OBJC_OBJECT))
.map(Clazz::getSootClass)
.findFirst()
.orElse(null);
Set<String> objClassHosts = new HashSet<>();
if (objCObjectClazz != null) {
// proceed if we link with ObjObject
// look for all ObjCClasses, and these are internal -- preload hosts as well
for (Clazz clazz : classes) {
SootClass sootClass = clazz.getSootClass();
// skip if doesn't extend ObjCClass
if (!SootClassUtils.isAssignableFrom(sootClass, objCObjectClazz))
continue;
// skip if not inner
String hostClassName = SootClassUtils.getEnclosingClassName(sootClass);
if (hostClassName == null)
hostClassName = SootClassUtils.getDeclaringClassName(sootClass);
if (hostClassName != null)
objClassHosts.add(hostClassName);
}

if (!objClassHosts.isEmpty()) {
try {
byte[] bytes = StringUtils.join(objClassHosts, ",").getBytes("UTF8");
linker.addRuntimeData(OBJC_CLASS + ".preloadClasses", bytes);
} catch (UnsupportedEncodingException e) {
config.getLogger().error("Failed to prepare ObjCClass' hosts preload list");
}
}
}
}
}

public static class MethodCompiler extends AbstractMethodCompiler {
// structure to expose CustomClasses to objc side
// check https://opensource.apple.com/source/objc4/objc4-437/runtime/objc-runtime-new.h for details
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.robovm.compiler.util.generic;

import soot.SootClass;
import soot.SootMethod;
import soot.SootResolver;
import soot.tagkit.EnclosingMethodTag;
import soot.tagkit.InnerClassTag;
import soot.tagkit.Tag;

public final class SootClassUtils {
private SootClassUtils() {
}

public static String getDeclaringClassName(SootClass sootClass) {
for (Tag tag : sootClass.getTags()) {
if (tag instanceof InnerClassTag) {
InnerClassTag icTag = (InnerClassTag) tag;
if (icTag.getInnerClass() != null && icTag.getOuterClass() != null) {
String innerName = icTag.getInnerClass().replace('/', '.');
if (sootClass.getName().equals(innerName)) {
return icTag.getOuterClass().replace('/', '.');
}
}
}
}
return null;
}

public static String getEnclosingClassName(SootClass sootClass) {
EnclosingMethodTag emTag = (EnclosingMethodTag) sootClass.getTag("EnclosingMethodTag");
if (emTag != null) {
return emTag.getEnclosingClass();
}
return null;
}

/**
* checks if sootClass can be assigned with subSootClass (e.g. if subSootClass extends sootClass)
*/
public static boolean isAssignableFrom(SootClass subSootClass, SootClass sootClass) {
if (sootClass.equals(subSootClass)) {
return true;
}

if (subSootClass.hasSuperclass()) {
return isAssignableFrom(subSootClass.getSuperclass(), sootClass);
}

return false;
}
}
26 changes: 26 additions & 0 deletions compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.robovm.objc;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -56,6 +57,31 @@ public final class ObjCClass extends ObjCObject {

static {
ObjCRuntime.bind(ObjCClass.class);

// TODO: remove once resolved on Idea side
// affects only debug builds
// workaround for Idea Bug: https://youtrack.jetbrains.com/issue/IDEA-332794
// there is code bellow loads all ObjCClass' and some of them might be Inner ones
// idea seems to be ignoring PREPARE class events in case Inner class is
// being loaded before the host one
byte[] data = VM.getRuntimeData(ObjCClass.class.getName() + ".preloadClasses");
if (data != null) {
String[] innerClassHosts;
try {
innerClassHosts = new String(data, "UTF8").split(",");
} catch (UnsupportedEncodingException e) {
innerClassHosts = new String[0];
}
for (String host : innerClassHosts) {
try {
// preload class.
Class.forName(host);
} catch (Throwable t) {
System.err.println("Failed to preload inner ObjCClass host class " + host + ": " + t.getMessage());
}
}
}

@SuppressWarnings("unchecked")
Class<? extends ObjCObject>[] classes = (Class<? extends ObjCObject>[])
VM.listClasses(ObjCObject.class, ClassLoader.getSystemClassLoader());
Expand Down

0 comments on commit f8a81f5

Please sign in to comment.