From 6be7910426d9a86f0367159bbd5c5aa9f4191de9 Mon Sep 17 00:00:00 2001 From: dkimitsa Date: Sun, 8 Oct 2023 15:34:15 +0300 Subject: [PATCH] * workaround: idea debugger doesn't stop in Inner classes that extends from ObjCObject ## 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 --- .../plugin/objc/ObjCMemberPlugin.java | 46 +++++++++++++++++ .../compiler/util/generic/SootClassUtils.java | 51 +++++++++++++++++++ .../main/java/org/robovm/objc/ObjCClass.java | 26 ++++++++++ 3 files changed, 123 insertions(+) create mode 100644 compiler/compiler/src/main/java/org/robovm/compiler/util/generic/SootClassUtils.java 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());