Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
<version>5.14.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.14.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockbukkit.mockbukkit</groupId>
<artifactId>mockbukkit-v1.21</artifactId>
Expand Down Expand Up @@ -113,7 +119,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
<configuration>
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar} -Xshare:off</argLine>
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar} -javaagent:${net.bytebuddy:byte-buddy-agent:jar} -Xshare:off</argLine>
<failIfNoTests>true</failIfNoTests>
</configuration>
</plugin>
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/net/tfminecraft/surgery/SurgeryPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void onDisable() {
// a new classloader, so the instanceof check in the listener no longer
// matches and players could pull items out of stale menus
for (Player player : getServer().getOnlinePlayers()) {
if (player.getOpenInventory().getTopInventory().getHolder() instanceof SurgeryMenuHolder) {
if (player.getOpenInventory().getTopInventory().getHolder(false) instanceof SurgeryMenuHolder) {
player.closeInventory();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ private void reportFailure(Player surgeon, String message, String extra) {

private Inventory surgeryMenu(Player surgeon, Outcome outcome) {
Inventory menu = surgeon.getOpenInventory().getTopInventory();
return menu.getHolder() instanceof SurgeryMenuHolder holder && holder.matches(surgeon, outcome.operationId())
return menu.getHolder(false) instanceof SurgeryMenuHolder holder && holder.matches(surgeon, outcome.operationId())
? menu : null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public void openSurgeryMenu(Player surgeon, Player patient, HealingInjury ailmen
// Checks if an inventory is the surgery menu (by holder, not title)
// ==============================================
public boolean isSurgeryMenu(org.bukkit.inventory.Inventory inventory) {
return inventory != null && inventory.getHolder() instanceof SurgeryMenuHolder;
return inventory != null && inventory.getHolder(false) instanceof SurgeryMenuHolder;
}

// ==============================================
Expand All @@ -103,7 +103,7 @@ public void handleItemClick(Player player, Inventory inventory, ItemStack clicke
}

private boolean ownsMenu(Player player, Inventory inventory) {
return inventory.getHolder() instanceof SurgeryMenuHolder holder
return inventory.getHolder(false) instanceof SurgeryMenuHolder holder
&& holder.matches(player, stateManager.getOperationId(player.getUniqueId()));
}

Expand Down
91 changes: 91 additions & 0 deletions src/test/java/net/tfminecraft/surgery/HolderLookupPatch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package net.tfminecraft.surgery;

import static net.bytebuddy.matcher.ElementMatchers.is;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import java.util.concurrent.atomic.AtomicBoolean;

import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.utility.JavaModule;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;
import org.mockbukkit.mockbukkit.inventory.InventoryMock;

/**
* MockBukkit throws on Paper's {@code getHolder(false)}. Its inventories hold no block snapshot,
* so the patch answers with the plain holder, which is what Paper returns for plugin GUIs.
*/
public final class HolderLookupPatch implements LauncherSessionListener {
private static boolean installed;

@Override
public void launcherSessionOpened(LauncherSession session) {
install();
}

/** Patches {@link InventoryMock#getHolder(boolean)} once per test JVM. */
public static synchronized void install() {
if (installed) {
return;
}
AtomicBoolean patched = new AtomicBoolean();
AtomicBoolean rejected = new AtomicBoolean();
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
.with(new AgentBuilder.Listener.Adapter() {
@Override
public void onTransformation(TypeDescription type, ClassLoader loader, JavaModule module,
boolean loaded, DynamicType dynamicType) {
if (type.getName().equals(InventoryMock.class.getName())) {
patched.set(true);
}
}

// The JVM can still reject the new bytes after onTransformation.
@Override
public void onError(String typeName, ClassLoader loader, JavaModule module, boolean loaded,
Throwable throwable) {
if (typeName.equals(InventoryMock.class.getName())) {
rejected.set(true);
}
}
})
.type(is(InventoryMock.class))
.transform((builder, type, loader, module, domain) -> builder.visit(Advice.to(PlainHolder.class)
.on(named("getHolder").and(takesArguments(boolean.class)))))
.installOn(ByteBuddyAgent.install());
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
// A failed retransformation only reaches the listeners, so check it took before marking it done.
if (!patched.get() || rejected.get()) {
throw new IllegalStateException("Could not patch InventoryMock#getHolder(boolean)");
}
installed = true;
}

/** Skips the throwing body and returns {@code getHolder()} instead. */
static final class PlainHolder {
private PlainHolder() {
}

@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean skip() {
return true;
}

@Advice.OnMethodExit
static void exit(@Advice.This Inventory inventory,
@Advice.Return(readOnly = false) InventoryHolder holder) {
holder = inventory.getHolder();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
net.tfminecraft.surgery.HolderLookupPatch