Skip to content

Commit d7777e3

Browse files
committed
UiSignEditInteraction: Fix multiple reflection issues
* Replaced too-wide catch of all `Exceptions` with granular catches * Most critically, stop discarding `InvocationTargetException` until it propagates to the top-level `SignEditorInvocationException` wrapper. * Add `SignSide` support to `openSignEditorWithReflection()`, just in case. * `sendSignUpdate()` needs an early return when it succeeds or else an incorrect `NoSuchMethodException` will be thrown. Issue: #42
1 parent fd9549b commit d7777e3

File tree

2 files changed

+60
-25
lines changed

2 files changed

+60
-25
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
## v1.14.2 (UNRELEASED)
1111

12+
### Fixed
13+
14+
* `/sign ui` and the equivalent right-click override may incorrectly be hiding issues with the player opening the sign editor. (#42)
15+
1216
### Under the Hood
1317

1418
* Remove ProGuard to improve the readability of stack traces
19+
* Narrow down potential `/sign ui` issues by reducing the `catch` scopes to just methods and fields that are missing when opening the native sign editor GUI
1520

1621
## v1.14.1 (2023-07-20)
1722

src/net/deltik/mc/signedit/interactions/UiSignEditInteraction.java

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public void interact(Player player, SignShim sign, SideShim side) {
151151

152152
try {
153153
openSignEditor(player, signImpl, side);
154-
} catch (Exception e) {
154+
} catch (NoSuchFieldException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
155155
formatSignForSave(player, signImpl, side);
156156
throw new SignEditorInvocationException(e);
157157
}
@@ -163,15 +163,19 @@ public void interact(Player player, SignShim sign, SideShim side) {
163163
* @param player The player that wants to open the sign editor
164164
* @param sign The sign that should load into the player's sign editor
165165
* @param side The side of the sign to open (ignored in Bukkit 1.19.4 and older)
166-
* @throws Exception if anything goes wrong while trying to open the sign editor
166+
* @throws NoSuchFieldException if something goes wrong while trying to open the sign editor
167+
* @throws InvocationTargetException if something goes wrong while trying to open the sign editor
168+
* @throws NoSuchMethodException if something goes wrong while trying to open the sign editor
169+
* @throws IllegalAccessException if something goes wrong while trying to open the sign editor
167170
*/
168-
private void openSignEditor(Player player, Sign sign, SideShim side) throws Exception {
171+
private void openSignEditor(Player player, Sign sign, SideShim side)
172+
throws NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
169173
try {
170174
if (openSignEditorBukkit1_20(player, sign, side)) return;
171175

172176
openSignEditorBukkit1_18(player, sign);
173-
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
174-
openSignEditorWithReflection(player, sign);
177+
} catch (NoSuchMethodException ignored) {
178+
openSignEditorWithReflection(player, sign, side);
175179
}
176180
}
177181

@@ -184,7 +188,6 @@ private void openSignEditor(Player player, Sign sign, SideShim side) throws Exce
184188
* @throws IllegalAccessException if the "openSign" method cannot be accessed due to Java reflection restrictions
185189
* @throws InvocationTargetException if an exception occurs while invoking the "openSign" method
186190
*/
187-
@SuppressWarnings("JavaReflectionMemberAccess")
188191
private static void openSignEditorBukkit1_18(Player player, Sign sign)
189192
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
190193
Method method = Player.class.getMethod("openSign", Sign.class);
@@ -198,18 +201,19 @@ private static void openSignEditorBukkit1_18(Player player, Sign sign)
198201
* @param sign The sign that should load into the player's sign editor
199202
* @param side The player's viewing side for the sign
200203
* @return true if the sign editor was successfully opened, false otherwise
201-
* @throws NoSuchMethodException if the "openSign" method cannot be found in the Player class
204+
* @throws NoSuchMethodException if the "openSign" method does not match the expected signature
202205
* @throws IllegalAccessException if the "openSign" method cannot be accessed due to Java reflection restrictions
203206
* @throws InvocationTargetException if an exception occurs while invoking the "openSign" method
204207
*/
205208
private static boolean openSignEditorBukkit1_20(Player player, Sign sign, SideShim side)
206209
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
207210
try {
208211
setPlayerWhoMayEditSign(player, sign);
209-
} catch (Exception ignored) {
212+
} catch (NoSuchFieldException ignored) {
213+
// Ignore PaperMC implementation detail
210214
}
211215

212-
Optional<Method> optionalMethod = Arrays.stream(Player.class.getDeclaredMethods())
216+
Optional<Method> optionalMethod = Arrays.stream(player.getClass().getMethods())
213217
.filter(method -> method.getName().equals("openSign") && method.getParameterCount() == 2)
214218
.filter(method -> {
215219
Class<?>[] parameterTypes = method.getParameterTypes();
@@ -236,7 +240,8 @@ private static boolean openSignEditorBukkit1_20(Player player, Sign sign, SideSh
236240
* @param player The player that wants to open the sign editor
237241
* @param sign The sign that should load into the player's sign editor
238242
*/
239-
private static void setPlayerWhoMayEditSign(Player player, Sign sign) throws Exception {
243+
private static void setPlayerWhoMayEditSign(Player player, Sign sign)
244+
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, NoSuchFieldException {
240245
Object tileEntitySign = toRawTileEntity(sign);
241246

242247
Field playerWhoMayEdit = getFirstFieldOfType(tileEntitySign, UUID.class, Modifier.PUBLIC);
@@ -245,41 +250,65 @@ private static void setPlayerWhoMayEditSign(Player player, Sign sign) throws Exc
245250
}
246251

247252
/**
248-
* Take a reflection-based guess to open the sign editor for common CraftBukkit implementations prior to Bukkit 1.18
253+
* Take a reflection-based guess to open the sign editor for common CraftBukkit implementations that are missing a
254+
* Bukkit stable API method to open the sign editor, i.e., no <code>Player#openSign(Sign, Side)</code> method.
249255
* <p>
250256
* Prior to Bukkit 1.18, there was no stable API to open the sign editor.
251-
* Instead, there was a method called <code>EntityHuman#openSign</code> available since CraftBukkit 1.8, which took
252-
* a <code>TileEntitySign</code> as its argument.
253-
* This method tries to call that unstable API method.
257+
* Instead, there was a method called <code>EntityHuman#openSign(TileEntitySign)</code> available since CraftBukkit
258+
* 1.8.
259+
* <p>
260+
* CraftBukkit 1.20 replaced that method with <code>EntityHuman#openSign(TileEntitySign, boolean)</code> where the
261+
* boolean parameter indicates the side of the sign to open: <code>true</code> for the front and <code>false</code>
262+
* for the back.
263+
* <p>
264+
* This method tries to call one of those unstable API methods.
254265
*
255266
* @param player The player that wants to open the sign editor
256267
* @param sign The sign that should load into the player's sign editor
257-
* @throws Exception if anything goes wrong while trying to open the sign editor
268+
* @param side The side of the sign to open (unused in Bukkit 1.19.4 and older)
269+
* @throws NoSuchFieldException if something goes wrong while trying to open the sign editor
270+
* @throws InvocationTargetException if something goes wrong while trying to open the sign editor
271+
* @throws NoSuchMethodException if something goes wrong while trying to open the sign editor
272+
* @throws IllegalAccessException if something goes wrong while trying to open the sign editor
258273
*/
259-
private void openSignEditorWithReflection(Player player, Sign sign) throws Exception {
274+
private void openSignEditorWithReflection(Player player, Sign sign, SideShim side)
275+
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, NoSuchFieldException {
260276
Object tileEntitySign = toRawTileEntity(sign);
261277
Object entityPlayer = toRawEntity(player);
262278

263-
makeTileEntitySignEditable(tileEntitySign);
279+
try {
280+
makeTileEntitySignEditable(tileEntitySign);
281+
} catch (NoSuchFieldException e) {
282+
setPlayerWhoMayEditSign(player, sign);
283+
}
264284

265-
Method openSignMethod = findMethodByParameterTypes(
266-
entityPlayer.getClass(), tileEntitySign.getClass()
267-
);
268-
openSignMethod.invoke(entityPlayer, tileEntitySign);
285+
try {
286+
Method openSignMethod = findMethodByParameterTypes(
287+
entityPlayer.getClass(), tileEntitySign.getClass()
288+
);
289+
openSignMethod.invoke(entityPlayer, tileEntitySign);
290+
} catch (NoSuchMethodException e) {
291+
Method openSignMethod = findMethodByParameterTypes(
292+
entityPlayer.getClass(), tileEntitySign.getClass(), boolean.class
293+
);
294+
openSignMethod.invoke(entityPlayer, tileEntitySign, SideShim.FRONT.equals(side));
295+
}
269296
}
270297

271-
private static Object toRawEntity(Entity entity) throws Exception {
298+
private static Object toRawEntity(Entity entity)
299+
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
272300
return getDeclaredMethodRecursive(entity.getClass(), "getHandle").invoke(entity);
273301
}
274302

275-
private static Object toRawTileEntity(BlockState blockState) throws Exception {
303+
private static Object toRawTileEntity(BlockState blockState)
304+
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
276305
return getDeclaredMethodRecursive(blockState.getClass(), "getTileEntity").invoke(blockState);
277306
}
278307

279308
/**
280309
* FIXME: Find a more reliable way than looking for the first public boolean to mark the TileEntitySign as editable
281310
*/
282-
private void makeTileEntitySignEditable(Object tileEntitySign) throws Exception {
311+
private void makeTileEntitySignEditable(Object tileEntitySign) throws NoSuchFieldException, IllegalAccessException {
283312
Field signIsEditable = getFirstFieldOfType(tileEntitySign, boolean.class, Modifier.PUBLIC);
284313
signIsEditable.setAccessible(true);
285314
signIsEditable.set(tileEntitySign, true);
@@ -305,9 +334,10 @@ private void formatSignForSave(Player player, Sign sign, SideShim side) {
305334

306335
private static void sendSignUpdate(Player player, Sign sign, ISignSide signSide) {
307336
try {
308-
for (Method method : Player.class.getDeclaredMethods()) {
337+
for (Method method : player.getClass().getMethods()) {
309338
if (method.getName().equals("sendBlockUpdate") && method.getParameterCount() == 2) {
310339
method.invoke(player, sign.getLocation(), sign);
340+
return;
311341
}
312342
}
313343
throw new NoSuchMethodException();

0 commit comments

Comments
 (0)