diff --git a/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java b/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java index fd3ee528d8..921158b544 100644 --- a/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java +++ b/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java @@ -364,8 +364,10 @@ public class GadpClientServerTest implements AsyncTestUtils { public TestGadpTargetMethod(TestTargetObject parent, String key) { super(parent.getModel(), parent, key, "Method"); - setAttributes(Map.of(PARAMETERS_ATTRIBUTE_NAME, PARAMS, RETURN_TYPE_ATTRIBUTE_NAME, - Integer.class), "Initialized"); + setAttributes(Map.of( + PARAMETERS_ATTRIBUTE_NAME, PARAMS, + RETURN_TYPE_ATTRIBUTE_NAME, Integer.class), + "Initialized"); } @Override @@ -381,6 +383,37 @@ public class GadpClientServerTest implements AsyncTestUtils { } } + private static final TargetParameterMap ORDERED_PARAMS = TargetMethod.makeParameters( + ParameterDescription.create(String.class, "H", true, "", "H", "H"), + ParameterDescription.create(String.class, "e", true, "", "e", "e"), + ParameterDescription.create(String.class, "l", true, "", "l", "l"), + ParameterDescription.create(String.class, "m", true, "", "l", "l"), + ParameterDescription.create(String.class, "o", true, "", "o", "o"), + ParameterDescription.create(String.class, "W", true, "", "W", "W"), + ParameterDescription.create(String.class, "p", true, "", "o", "o"), + ParameterDescription.create(String.class, "r", true, "", "r", "r"), + ParameterDescription.create(String.class, "n", true, "", "l", "l"), + ParameterDescription.create(String.class, "d", true, "", "d", "d")); + + public class TestGadpTargetMethodWithManyParameters + extends TestTargetObject> implements TargetMethod { + + public TestGadpTargetMethodWithManyParameters(TestTargetObject parent, String key) { + super(parent.getModel(), parent, key, "MethodPlus"); + + setAttributes(Map.of( + PARAMETERS_ATTRIBUTE_NAME, ORDERED_PARAMS, + RETURN_TYPE_ATTRIBUTE_NAME, Integer.class), + "Initialized"); + } + + @Override + public CompletableFuture invoke(Map arguments) { + fail("I wasn't expecting that"); + return AsyncUtils.nil(); + } + } + @TargetObjectSchemaInfo(name = "ProcessContainer") public class TestGadpTargetProcessContainer extends TestTargetObject @@ -473,8 +506,10 @@ public class GadpClientServerTest implements AsyncTestUtils { public TestGadpTargetAvailableContainer(TestGadpTargetSession session) { super(session.getModel(), session, "Available", "AvailableContainer"); - setAttributes(List.of(new TestGadpTargetMethod(this, "greet")), Map.of(), - "Initialized"); + setAttributes(List.of( + new TestGadpTargetMethod(this, "greet"), + new TestGadpTargetMethodWithManyParameters(this, "bigGreet")), + Map.of(), "Initialized"); } @Override @@ -815,6 +850,32 @@ public class GadpClientServerTest implements AsyncTestUtils { } } + @Test + public void testMethodParametersOrderPreserved() throws Throwable { + AsynchronousSocketChannel socket = socketChannel(); + try (ServerRunner runner = new ServerRunner()) { + GadpClient client = new GadpClient("Test", socket); + waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect, + runner.server.getLocalAddress())); + waitOn(client.connect()); + TargetObject greet = + waitOn(client.fetchModelObject(PathUtils.parse("Available.bigGreet"))); + assertTrue(greet.getInterfaceNames().contains("Method")); + TargetMethod method = greet.as(TargetMethod.class); + + TargetParameterMap params = method.getParameters(); + assertEquals(ORDERED_PARAMS, params); + + assertEquals("HelmoWprnd", + params.values().stream().map(p -> p.name).collect(Collectors.joining())); + + waitOn(client.close()); + } + finally { + socket.close(); + } + } + @Test public void testListRoot() throws Throwable { AsynchronousSocketChannel socket = socketChannel(); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetMethod.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetMethod.java index 2647535b3d..e6e723b137 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetMethod.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetMethod.java @@ -201,7 +201,10 @@ public interface TargetMethod extends TargetObject { * @return a map of descriptions by name */ static TargetParameterMap makeParameters(Stream> params) { - return TargetParameterMap.copyOf(params.collect(Collectors.toMap(p -> p.name, p -> p))); + return TargetParameterMap + .copyOf(params.collect(Collectors.toMap(p -> p.name, p -> p, (a, b) -> { + throw new IllegalArgumentException("duplicate parameters: " + a + " and " + b); + }, LinkedHashMap::new))); } /** diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/CollectionUtils.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/CollectionUtils.java index 5487e92800..d80f13b855 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/CollectionUtils.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/CollectionUtils.java @@ -204,7 +204,7 @@ public enum CollectionUtils { protected final Map wrapped; public AbstractNMap(Map map) { - this.wrapped = Map.copyOf(map); + this.wrapped = Collections.unmodifiableMap(new LinkedHashMap<>(map)); } @Override