From 64f0bd832b469ef5d70329261cd86c5859b3f64c Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 9 Feb 2025 14:32:03 -0600 Subject: [PATCH] Smash through module restrictions by directly setting on the fallback classloader --- build.gradle | 47 +++----- .../IntegratedScripting.java | 62 +++++++++- .../integratedscripting/UnsafeHelper.java | 107 ++++++++++++++++++ .../evaluate/ScriptHelpers.java | 17 ++- src/main/resources/graaldeps.json | 3 + 5 files changed, 201 insertions(+), 35 deletions(-) create mode 100644 src/main/java/org/cyclops/integratedscripting/UnsafeHelper.java create mode 100644 src/main/resources/graaldeps.json diff --git a/build.gradle b/build.gradle index 1b4fcaa0..b61ba836 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,6 @@ plugins { id 'net.darkhax.curseforgegradle' version '1.0.8' id 'com.github.kt3k.coveralls' version '2.12.0' id 'com.diffplug.spotless' version '6.25.0' - id 'com.github.johnrengelman.shadow' version '8.1.1' id 'com.modrinth.minotaur' version '2.+' } @@ -89,6 +88,7 @@ sourceSets.main.resources { srcDir 'src/generated/resources' } configurations { modLib + graaldeps implementation.extendsFrom modLib } @@ -142,13 +142,13 @@ dependencies { } // https://mvnrepository.com/artifact/org.graalvm.sdk/graal-sdk - modLib "org.graalvm.sdk:graal-sdk:${project.graal_version}" - shadow "org.graalvm.sdk:graal-sdk:${project.graal_version}" + compileOnly(testImplementation("org.graalvm.sdk:graal-sdk:${project.graal_version}")) + graaldeps "org.graalvm.sdk:graal-sdk:${project.graal_version}" // https://mvnrepository.com/artifact/org.graalvm.js/js - modLib ("org.graalvm.js:js:${project.graal_version}") { + compileOnly(testImplementation("org.graalvm.js:js:${project.graal_version}") { exclude group: 'com.ibm.icu', module: 'icu4j' - } - shadow "org.graalvm.js:js:${project.graal_version}" + }) + graaldeps "org.graalvm.js:js:${project.graal_version}" // Project lombok @@ -257,26 +257,8 @@ task javadocJar(type: Jar, dependsOn: javadoc) { from javadoc.destinationDir } -shadowJar { - mergeServiceFiles() // To fix graal issue: https://github.com/oracle/graaljs/issues/125 - configurations = [project.configurations.shadow] - archiveClassifier.set(''); // Replace the default JAR - // To avoid clashes with other mods - relocate 'org.graalvm', 'org.cyclops.integratedscripting.vendors.org.graalvm' - relocate 'com.ibm', 'org.cyclops.integratedscripting.vendors.com.ibm' - // Relocate everything from com.oracle, except for com.oracle.truffle, as this is defined in a native lib, which can not be relocated -// relocate 'com.oracle', 'org.cyclops.integratedscripting.vendors.com.oracle' - relocate 'com.oracle.js', 'org.cyclops.integratedscripting.vendors.com.oracle.js' - relocate 'com.oracle.svm', 'org.cyclops.integratedscripting.vendors.com.oracle.svm' -// relocate 'com.oracle.truffle', 'org.cyclops.integratedscripting.vendors.com.oracle.truffle' // Relocation of this fails for com.oracle.truffle.runtime.ModulesSupport at runtime -} -assemble.dependsOn shadowJar -jar { - shadowJar {} -} - artifacts { - archives shadowJar + archives jar archives deobfJar archives sourcesJar archives javadocJar @@ -304,19 +286,24 @@ processResources { 'commoncapabilities_version' : commoncapabilities_version, 'commoncapabilities_version_semver' : commoncapabilities_version.replaceAll("-.*\$", ""), 'integrateddynamics_version' : integrateddynamics_version, - 'integrateddynamics_version_semver' : integrateddynamics_version.replaceAll("-.*\$", "") + 'integrateddynamics_version_semver' : integrateddynamics_version.replaceAll("-.*\$", ""), + 'graaldeps' : configurations.graaldeps.files.findAll { it.name.endsWith(".jar") }.collect { "\"META-INF/graaldeps/${it.name}\"" }.join(", ") ] - filesMatching(['pack.mcmeta', 'META-INF/mods.toml', 'META-INF/neoforge.mods.toml', 'mixins.*.json']) { + filesMatching(['pack.mcmeta', 'META-INF/mods.toml', 'META-INF/neoforge.mods.toml', 'mixins.*.json', 'graaldeps.json']) { expand expandProps } + from(configurations.graaldeps.files) { + include("*.jar") + into "META-INF/graaldeps" + } inputs.properties(expandProps) } task publishCurseForge(type: TaskPublishCurseForge) { dependsOn(tasks.jar) apiToken = secrets.curseforgeKey; - def mainFile = upload(project.curseforge_project_id, shadowJar) + def mainFile = upload(project.curseforge_project_id, jar) mainFile.releaseType = secrets.build_number.equals("RELEASE") ? Constants.RELEASE_TYPE_RELEASE : Constants.RELEASE_TYPE_BETA mainFile.changelogType = "text" mainFile.changelog = secrets.changelog @@ -334,7 +321,7 @@ modrinth { versionNumber = project.minecraft_version + '-' + project.version versionName = "${project.version} for NeoForge ${project.minecraft_version}" versionType = secrets.build_number.equals("RELEASE") ? "release" : "beta" - uploadFile = shadowJar + uploadFile = jar gameVersions = [ project.minecraft_version ] changelog = provider { secrets.changelog } @@ -371,7 +358,7 @@ publishing { publications { PublicationContainer publicationContainer -> publicationContainer.register("maven", MavenPublication) { MavenPublication publication -> - publication.artifacts = [shadowJar, javadocJar, deobfJar, sourcesJar] + publication.artifacts = [jar, javadocJar, deobfJar, sourcesJar] publication.artifactId = project.archivesBaseName.toLowerCase() // GH can't handle uppercase... } } diff --git a/src/main/java/org/cyclops/integratedscripting/IntegratedScripting.java b/src/main/java/org/cyclops/integratedscripting/IntegratedScripting.java index be1fa815..23ba84c4 100644 --- a/src/main/java/org/cyclops/integratedscripting/IntegratedScripting.java +++ b/src/main/java/org/cyclops/integratedscripting/IntegratedScripting.java @@ -1,9 +1,13 @@ package org.cyclops.integratedscripting; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import lombok.SneakyThrows; import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; +import net.minecraft.util.GsonHelper; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.ItemStack; import net.neoforged.api.distmarker.Dist; @@ -13,11 +17,13 @@ import net.neoforged.fml.common.Mod; import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; import net.neoforged.fml.event.lifecycle.FMLLoadCompleteEvent; +import net.neoforged.fml.loading.FMLLoader; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; import net.neoforged.neoforge.registries.NewRegistryEvent; +import org.apache.commons.io.function.IOStream; import org.apache.logging.log4j.Level; import org.cyclops.cyclopscore.config.ConfigHandler; import org.cyclops.cyclopscore.helper.MinecraftHelpers; @@ -49,6 +55,16 @@ import org.cyclops.integratedscripting.part.PartTypes; import org.cyclops.integratedscripting.proxy.ClientProxy; import org.cyclops.integratedscripting.proxy.CommonProxy; +import org.graalvm.polyglot.Context; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; /** * The main mod class of this mod. @@ -57,7 +73,6 @@ */ @Mod(Reference.MOD_ID) public class IntegratedScripting extends ModBaseVersionable { - public static IntegratedScripting _instance; public ScriptingData scriptingData; @@ -65,6 +80,8 @@ public class IntegratedScripting extends ModBaseVersionable public IntegratedScripting(IEventBus modEventBus) { super(Reference.MOD_ID, (instance) -> _instance = instance, modEventBus); + loadGraal(); + getRegistryManager().addRegistry(IValueTranslatorRegistry.class, ValueTranslatorRegistry.getInstance()); getRegistryManager().addRegistry(ILanguageHandlerRegistry.class, LanguageHandlerRegistry.getInstance()); @@ -191,4 +208,47 @@ public static void clog(Level level, String message) { IntegratedScripting._instance.getLoggerHelper().log(level, message); } + @SneakyThrows + private static void loadGraal() { + JsonObject jo; + Path tmp = FMLLoader.getGamePath().resolve("config").resolve("integratedscripting-tmp"); + Files.createDirectories(tmp); + Set outFiles = new HashSet<>(); + try (InputStream is = IntegratedScripting.class.getResourceAsStream("/graaldeps.json")) { + assert is != null; + jo = GsonHelper.parse(new InputStreamReader(is)); + for (JsonElement path : jo.get("paths").getAsJsonArray()) { + // extract to temp dir + String pathAsString = "/" + path.getAsString(); + Path outFile = tmp.resolve(pathAsString.substring(pathAsString.lastIndexOf('/') + 1)); + outFiles.add(outFile); + try (InputStream jarStream = IntegratedScripting.class.getResourceAsStream(pathAsString)) { + assert jarStream != null; + Files.copy(jarStream, outFile, StandardCopyOption.REPLACE_EXISTING); + } + } + } + + try (Stream files = Files.list(tmp)) { + IOStream.adapt(files).filter(file -> !outFiles.contains(file)).forEach(Files::deleteIfExists); + } + + for (Path outFile : outFiles) { + UnsafeHelper.addToFallbackClassloader(outFile); + } + + System.out.println(outFiles.size() + " Graal dependencies loaded"); + ClassLoader f = UnsafeHelper.makeFallbackClassloader(); + ClassLoader p = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(f); + try { + Context.Builder build = Context.newBuilder("js"); + Context con = build.build(); + con.eval("js", "console.log('js pre-loaded.')"); + con.close(); + } finally { + Thread.currentThread().setContextClassLoader(p); + } + } + } diff --git a/src/main/java/org/cyclops/integratedscripting/UnsafeHelper.java b/src/main/java/org/cyclops/integratedscripting/UnsafeHelper.java new file mode 100644 index 00000000..90222c3b --- /dev/null +++ b/src/main/java/org/cyclops/integratedscripting/UnsafeHelper.java @@ -0,0 +1,107 @@ +package org.cyclops.integratedscripting; + +import cpw.mods.cl.ModuleClassLoader; +import lombok.SneakyThrows; +import sun.misc.Unsafe; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Wagyourtail + */ +public class UnsafeHelper { + private static final Unsafe UNSAFE = getUnsafe(); + private static final MethodHandles.Lookup IMPL_LOOKUP = UnsafeHelper.getImplLookup(); + + private static final MethodHandle getFallbackClassLoader; + private static final MethodHandle setFallbackClassLoader; + + static { + try { + getFallbackClassLoader = IMPL_LOOKUP.findGetter(ModuleClassLoader.class, "fallbackClassLoader", ClassLoader.class); + setFallbackClassLoader = IMPL_LOOKUP.findSetter(ModuleClassLoader.class, "fallbackClassLoader", ClassLoader.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static Unsafe getUnsafe() { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (Unsafe) f.get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new UnsupportedOperationException("Unable to get Unsafe instance", e); + } + } + + private static MethodHandles.Lookup getImplLookup() { + try { + // ensure lookup is initialized + MethodHandles.Lookup lookup = MethodHandles.lookup(); + // get the impl_lookup field + Field implLookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); + Unsafe unsafe = getUnsafe(); + MethodHandles.Lookup IMPL_LOOKUP; + IMPL_LOOKUP = (MethodHandles.Lookup) unsafe.getObject(MethodHandles.Lookup.class, unsafe.staticFieldOffset(implLookupField)); + if (IMPL_LOOKUP != null) return IMPL_LOOKUP; + throw new NullPointerException(); + } catch (Throwable e) { + try { + // try to create a new lookup + Constructor constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class); + constructor.setAccessible(true); + return constructor.newInstance(Object.class, -1); + } catch (Throwable e2) { + e.addSuppressed(e2); + } + throw new UnsupportedOperationException("Unable to get MethodHandles.Lookup.IMPL_LOOKUP", e); + } + } + + @SneakyThrows + public static void addToFallbackClassloader(Path path) { + UnsafeFallbackClassLoader u = makeFallbackClassloader(); + u.addURL(path.toUri().toURL()); + } + + @SneakyThrows + public static UnsafeFallbackClassLoader makeFallbackClassloader() { + List loaders = new ArrayList<>(); + ClassLoader l = UnsafeHelper.class.getClassLoader(); + if (!(l instanceof ModuleClassLoader)) { + return new UnsafeFallbackClassLoader(l); + } + while (l instanceof ModuleClassLoader) { + loaders.add(l); + l = (ClassLoader) getFallbackClassLoader.invokeExact((ModuleClassLoader) l); + } + if (l instanceof UnsafeFallbackClassLoader) { + return (UnsafeFallbackClassLoader) l; + } + UnsafeFallbackClassLoader u = new UnsafeFallbackClassLoader(l); + setFallbackClassLoader.invokeExact((ModuleClassLoader) loaders.getLast(), (ClassLoader) u); + return u; + } + + public static class UnsafeFallbackClassLoader extends URLClassLoader { + public UnsafeFallbackClassLoader(ClassLoader parent) { + super(new URL[0], parent); + } + + @Override + public void addURL(URL url) { + super.addURL(url); + } + + } + +} diff --git a/src/main/java/org/cyclops/integratedscripting/evaluate/ScriptHelpers.java b/src/main/java/org/cyclops/integratedscripting/evaluate/ScriptHelpers.java index f7a3432e..1dcdf112 100644 --- a/src/main/java/org/cyclops/integratedscripting/evaluate/ScriptHelpers.java +++ b/src/main/java/org/cyclops/integratedscripting/evaluate/ScriptHelpers.java @@ -7,6 +7,7 @@ import org.cyclops.integrateddynamics.core.evaluate.operator.Operators; import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypeOperator; import org.cyclops.integratedscripting.GeneralConfig; +import org.cyclops.integratedscripting.UnsafeHelper; import org.cyclops.integratedscripting.api.evaluate.translation.IEvaluationExceptionFactory; import org.cyclops.integratedscripting.evaluate.translation.ValueTranslators; import org.graalvm.polyglot.Context; @@ -26,10 +27,18 @@ */ public class ScriptHelpers { - private static final Engine ENGINE = Engine - .newBuilder() - .option("engine.WarnInterpreterOnly", "false") - .build(); + private static final Engine ENGINE; + static { + ClassLoader c = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(UnsafeHelper.makeFallbackClassloader()); + try { + ENGINE = Engine.newBuilder() + .option("engine.WarnInterpreterOnly", "false") + .build(); + } finally { + Thread.currentThread().setContextClassLoader(c); + } + } public static Context createBaseContext(@Nullable Function contextBuilderModifier) { Context.Builder contextBuilder = Context diff --git a/src/main/resources/graaldeps.json b/src/main/resources/graaldeps.json new file mode 100644 index 00000000..5c66e94a --- /dev/null +++ b/src/main/resources/graaldeps.json @@ -0,0 +1,3 @@ +{ + "paths": [$graaldeps] +} \ No newline at end of file