Skip to content

Commit

Permalink
Transform listener classes to obtain privileged Lookups
Browse files Browse the repository at this point in the history
  • Loading branch information
Yeregorix committed Oct 21, 2023
1 parent 32cb65c commit 2af8927
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.spongepowered.forge.applaunch.loading.moddiscovery.library.LibraryManager;
import org.spongepowered.forge.applaunch.service.SpongeForgeTransformationService;
import org.spongepowered.forge.applaunch.transformation.SpongeForgeTransformationService;

import java.nio.file.Path;
import java.util.ArrayList;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.forge.applaunch.transformation;

import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;

import cpw.mods.modlauncher.api.ITransformer;
import cpw.mods.modlauncher.api.ITransformerActivity;
import cpw.mods.modlauncher.api.ITransformerVotingContext;
import cpw.mods.modlauncher.api.TransformerVoteResult;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import java.util.HashSet;
import java.util.Set;

public class ListenerTransformer implements ITransformer<ClassNode> {

@NonNull
@Override
public ClassNode transform(final ClassNode input, final ITransformerVotingContext context) {
MethodNode clinit = null;
for (final MethodNode method : input.methods) {
if (method.name.equals("<clinit>") && method.desc.equals("()V")) {
clinit = method;
break;
}
}

if (clinit == null) {
clinit = new MethodNode(ACC_STATIC, "<clinit>", "()V", null, null);
input.methods.add(clinit);
}


final InsnList list = new InsnList();
list.add(new LdcInsnNode(Type.getObjectType(input.name)));
list.add(new MethodInsnNode(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false));
list.add(new MethodInsnNode(INVOKESTATIC, "org/spongepowered/forge/launch/event/ListenerLookups", "set", "(Ljava/lang/Class;Ljava/lang/invoke/MethodHandles$Lookup;)V"));

clinit.instructions.insert(list);

return input;
}

@NonNull
@Override
public TransformerVoteResult castVote(final ITransformerVotingContext context) {
return context.getReason().equals(ITransformerActivity.CLASSLOADING_REASON) ? TransformerVoteResult.YES : TransformerVoteResult.NO;
}

@NonNull
@Override
public Set<Target> targets() {
final Type listenerType = Type.getType("Lorg/spongepowered/api/event/Listener;");

final Set<Type> listenerClasses = new HashSet<>();
for (ModFileInfo fileInfo : LoadingModList.get().getModFiles()) {
for (ModFileScanData.AnnotationData annotation : fileInfo.getFile().getScanResult().getAnnotations()) {
if (listenerType.equals(annotation.annotationType())) {
listenerClasses.add(annotation.clazz());
}
}
}

final Set<Target> targets = new HashSet<>();
for (Type listener : listenerClasses) {
targets.add(Target.targetClass(listener.getInternalName()));
}
return targets;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.forge.applaunch.service;
package org.spongepowered.forge.applaunch.transformation;

import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.Environment;
Expand All @@ -43,7 +43,6 @@

import java.net.MalformedURLException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -120,9 +119,9 @@ public List<Resource> completeScan(IModuleLayerManager layerManager) {

@NonNull
@Override
@SuppressWarnings("rawtypes") // :(((((((((((((((
@SuppressWarnings("rawtypes") // :)
public List<ITransformer> transformers() {
return Collections.emptyList();
return List.of(new ListenerTransformer());
}

public static final class Keys {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
org.spongepowered.forge.applaunch.service.SpongeForgeTransformationService
org.spongepowered.forge.applaunch.transformation.SpongeForgeTransformationService
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@
import net.minecraftforge.eventbus.api.IEventBusInvokeDispatcher;
import net.minecraftforge.eventbus.api.IEventListener;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.common.event.filter.FilterGenerator;
import org.spongepowered.common.event.manager.AnnotatedEventListener;
import org.spongepowered.common.event.manager.ClassEventListenerFactory;
import org.spongepowered.common.event.manager.RegisteredListener;
import org.spongepowered.common.event.manager.SpongeEventManager;
import org.spongepowered.forge.launch.bridge.event.ForgeEventBridge_Forge;
Expand Down Expand Up @@ -143,11 +140,8 @@ public void start() {
// EventManager

@Override
protected AnnotatedEventListener.Factory computeFactory(PluginContainer key) {
final MethodHandles.Lookup lookup;
// TODO SF 1.19.4, what lookup should we use?
lookup = SpongeEventManager.OWN_LOOKUP;
return new ClassEventListenerFactory(FilterGenerator::create, lookup);
protected MethodHandles.@Nullable Lookup getLookup(final PluginContainer plugin, final Class<?> handle) {
return ListenerLookups.get(handle);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.forge.launch.event;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ListenerLookups {
private static final Map<Class<?>, MethodHandles.Lookup> lookups = new ConcurrentHashMap<>();

public static MethodHandles.@Nullable Lookup get(final Class<?> listenerClass) {
return ListenerLookups.lookups.get(listenerClass);
}

public static void set(final Class<?> listenerClass, final MethodHandles.Lookup lookup) {
ListenerLookups.lookups.put(listenerClass, lookup);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,31 +44,31 @@
public class ForgePluginContainer implements PluginContainer {
private final ModContainer modContainer;

private Logger logger;
private PluginMetadata pluginMetadata;

ForgePluginContainer(final ModContainer modContainer) {
this.modContainer = modContainer;
}

private Logger forge$logger;
private PluginMetadata forge$pluginMetadata;

public ModContainer getModContainer() {
return this.modContainer;
}

@Override
public PluginMetadata metadata() {
if (this.forge$pluginMetadata == null) {
this.forge$pluginMetadata = ((ForgeLaunch) Launch.instance()).metadataForMod((ModInfo) this.modContainer.getModInfo());
if (this.pluginMetadata == null) {
this.pluginMetadata = ((ForgeLaunch) Launch.instance()).metadataForMod((ModInfo) this.modContainer.getModInfo());
}
return this.forge$pluginMetadata;
return this.pluginMetadata;
}

@Override
public Logger logger() {
if (this.forge$logger == null) {
this.forge$logger = LogManager.getLogger(this.modContainer.getModId());
if (this.logger == null) {
this.logger = LogManager.getLogger(this.modContainer.getModId());
}
return this.forge$logger;
return this.logger;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

import static com.google.common.base.Preconditions.checkNotNull;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.api.event.Event;
import org.spongepowered.common.event.SpongeEventListener;

Expand All @@ -48,7 +47,7 @@ public final Object getHandle() {
public interface Factory {

AnnotatedEventListener create(Object handle, ListenerClassVisitor.DiscoveredMethod method,
MethodHandles.@Nullable Lookup lookup) throws Throwable;
MethodHandles.Lookup lookup) throws Throwable;

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,16 @@
public final class ClassEventListenerFactory implements AnnotatedEventListener.Factory {

private static final String FILTER = "filter";
private final MethodHandles.Lookup lookup;
private final FilterFactory filterFactory;

public ClassEventListenerFactory(final FilterFactory factory, final MethodHandles.Lookup lookup) {
public ClassEventListenerFactory(final FilterFactory factory) {
this.filterFactory = Objects.requireNonNull(factory, "filterFactory");
this.lookup = Objects.requireNonNull(lookup, "lookup");
}

@Override
public AnnotatedEventListener create(final Object handle, final ListenerClassVisitor.DiscoveredMethod method,
final MethodHandles.@Nullable Lookup handleLookup) throws Throwable {
final MethodHandles.Lookup lookup = this.createLookup(method, handleLookup != null ? handleLookup : this.lookup);
final MethodHandles.Lookup handleLookup) throws Throwable {
final MethodHandles.Lookup lookup = this.createLookup(method, handleLookup);
return (AnnotatedEventListener) lookup.findConstructor(
lookup.lookupClass(),
MethodType.methodType(void.class, method.declaringClass())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.spongepowered.common.SpongeCommon;
import org.spongepowered.common.bridge.world.inventory.container.ContainerBridge;
import org.spongepowered.common.event.ShouldFire;
import org.spongepowered.common.event.filter.FilterGenerator;
import org.spongepowered.common.event.tracking.PhaseContext;
import org.spongepowered.common.event.tracking.PhaseTracker;
import org.spongepowered.common.event.tracking.phase.plugin.EventListenerPhaseContext;
Expand All @@ -63,7 +64,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
Expand All @@ -77,7 +77,7 @@
public abstract class SpongeEventManager implements EventManager {

private static final NoExceptionClosable NULL_CLOSABLE = new NoExceptionClosable();
protected static final MethodHandles.Lookup OWN_LOOKUP = MethodHandles.lookup();
private static final MethodHandles.Lookup OWN_LOOKUP = MethodHandles.lookup();

public final ListenerChecker checker;
private final Object lock;
Expand All @@ -89,13 +89,11 @@ public abstract class SpongeEventManager implements EventManager {
*/
protected final LoadingCache<EventType<?>, RegisteredListener.Cache> handlersCache =
Caffeine.newBuilder().initialCapacity(150).build(this::bakeHandlers);
private final Map<PluginContainer, AnnotatedEventListener.Factory> pluginFactories;
private final Set<Object> registeredListeners;

public SpongeEventManager() {
this.lock = new Object();
this.handlersByEvent = HashMultimap.create();
this.pluginFactories = new IdentityHashMap<>();
this.registeredListeners = new ReferenceOpenHashSet<>();
this.checker = new ListenerChecker(ShouldFire.class);

Expand Down Expand Up @@ -225,10 +223,10 @@ private void register(final RegisteredListener<? extends Event> handler) {
}
}

protected abstract AnnotatedEventListener.Factory computeFactory(final PluginContainer key);
protected abstract MethodHandles.@Nullable Lookup getLookup(final PluginContainer plugin, final Class<?> handle);

private void registerListener(final PluginContainer plugin, final Object listenerObject,
final MethodHandles.@Nullable Lookup lookup) {
final MethodHandles.@Nullable Lookup customLookup) {
Objects.requireNonNull(plugin, "plugin");
Objects.requireNonNull(listenerObject, "listener");

Expand All @@ -244,7 +242,16 @@ private void registerListener(final PluginContainer plugin, final Object listene

final Class<?> handle = listenerObject.getClass();

final AnnotatedEventListener.Factory handlerFactory = this.pluginFactories.computeIfAbsent(plugin, this::computeFactory);
MethodHandles.@Nullable Lookup lookup = customLookup;
if (lookup == null) {
lookup = this.getLookup(plugin, handle);
if (lookup == null) {
SpongeCommon.logger().warn("No lookup found for listener {}. Using Sponge's lookup as fallback.", handle.getName());
lookup = SpongeEventManager.OWN_LOOKUP;
}
}

final AnnotatedEventListener.Factory handlerFactory = new ClassEventListenerFactory(FilterGenerator::create);

try {
final List<ListenerClassVisitor.DiscoveredMethod> methods = ListenerClassVisitor.getEventListenerMethods(handle);
Expand All @@ -268,7 +275,7 @@ private void registerListener(final PluginContainer plugin, final Object listene
}
}
} catch (final IOException ioe) {
SpongeCommon.logger().warn("Exception trying to register superclass listeners", ioe);
SpongeCommon.logger().warn("Exception trying to register class listeners", ioe);
} catch (final NoSuchMethodException nsme) {
SpongeCommon.logger().warn("Discovered method listener somehow not found for class " + handle.getName(), nsme);
} catch (final ClassNotFoundException e) {
Expand Down
Loading

0 comments on commit 2af8927

Please sign in to comment.