diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/objc/ObjCMemberPlugin.java b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/objc/ObjCMemberPlugin.java index 0d3fd9c09..c5815c433 100755 --- a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/objc/ObjCMemberPlugin.java +++ b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/objc/ObjCMemberPlugin.java @@ -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; @@ -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; @@ -694,6 +696,7 @@ public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) @Override public void beforeLinker(Config config, Linker linker, Set classes) { preloadClassesForFramework(config, linker, classes); + preloadObjCClassHosts(config, linker, classes); } private static List l(E head, List tail) { @@ -2029,6 +2032,49 @@ private void preloadClassesForFramework(Config config, Linker linker, Set } } + private void preloadObjCClassHosts(Config config, Linker linker, Set 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 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 diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/util/generic/SootClassUtils.java b/compiler/compiler/src/main/java/org/robovm/compiler/util/generic/SootClassUtils.java new file mode 100644 index 000000000..818918496 --- /dev/null +++ b/compiler/compiler/src/main/java/org/robovm/compiler/util/generic/SootClassUtils.java @@ -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; + } +} diff --git a/compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java b/compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java index c6142d397..fd3009c02 100755 --- a/compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java +++ b/compiler/objc/src/main/java/org/robovm/objc/ObjCClass.java @@ -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; @@ -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[] classes = (Class[]) VM.listClasses(ObjCObject.class, ClassLoader.getSystemClassLoader());