GP-4298: Fix Trace RMI argument validation.

This commit is contained in:
Dan 2024-02-05 13:27:59 -05:00
parent 1bf2e3b151
commit b5ea1540c3
5 changed files with 237 additions and 13 deletions

View file

@ -106,23 +106,28 @@ public interface RemoteMethod {
* primitive. We instead need {@link TraceObject}. I'd add the method to the schema, except that * primitive. We instead need {@link TraceObject}. I'd add the method to the schema, except that
* trace stuff is not in its dependencies. * trace stuff is not in its dependencies.
* *
* @param name the name of the parameter * @param paramName the name of the parameter
* @param schName the name of the parameter's schema
* @param sch the type of the parameter * @param sch the type of the parameter
* @param arg the argument * @param arg the argument
*/ */
static void checkType(String name, TargetObjectSchema sch, Object arg) { static void checkType(String paramName, SchemaName schName, TargetObjectSchema sch,
if (sch.getType() != TargetObject.class) { Object arg) {
if (sch.getType().isInstance(arg)) { // if sch is null, it was definitely an object-type schema without context
return; if (sch != null) {
if (sch.getType() != TargetObject.class) {
if (sch.getType().isInstance(arg)) {
return;
}
} }
} else if (arg instanceof TraceObject obj) {
else if (arg instanceof TraceObject obj) { if (sch.isAssignableFrom(obj.getTargetSchema())) {
if (sch.equals(obj.getTargetSchema())) { return;
return; }
} }
} }
throw new IllegalArgumentException( throw new IllegalArgumentException(
"For parameter %s: argument %s is not a %s".formatted(name, arg, sch)); "For parameter %s: argument %s is not a %s".formatted(paramName, arg, schName));
} }
/** /**
@ -159,8 +164,9 @@ public interface RemoteMethod {
"All TraceObject parameters must come from the same trace"); "All TraceObject parameters must come from the same trace");
} }
} }
TargetObjectSchema sch = ctx.getSchema(ent.getValue().type()); SchemaName schName = ent.getValue().type();
checkType(ent.getKey(), sch, arg); TargetObjectSchema sch = ctx.getSchemaOrNull(schName);
checkType(ent.getKey(), schName, sch, arg);
} }
for (Map.Entry<String, Object> ent : arguments.entrySet()) { for (Map.Entry<String, Object> ent : arguments.entrySet()) {
if (!parameters().containsKey(ent.getKey())) { if (!parameters().containsKey(ent.getKey())) {
@ -198,6 +204,7 @@ public interface RemoteMethod {
* *
* @param arguments the keyword arguments to the remote method * @param arguments the keyword arguments to the remote method
* @throws IllegalArgumentException if the arguments are not valid * @throws IllegalArgumentException if the arguments are not valid
* @return the returned value
*/ */
default Object invoke(Map<String, Object> arguments) { default Object invoke(Map<String, Object> arguments) {
try { try {

View file

@ -0,0 +1,190 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.tracermi;
import static ghidra.app.plugin.core.debug.gui.model.DebuggerModelProviderTest.CTX;
import java.util.Map;
import org.junit.Test;
import db.Transaction;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteMethod;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteParameter;
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.debug.api.target.ActionName;
import ghidra.debug.api.tracermi.RemoteMethod;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.target.*;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
public class RemoteMethodTest extends AbstractGhidraHeadedDebuggerTest {
@Test
public void testRemoteMethodValidateObjectGivenObject() throws Throwable {
RemoteMethod method = new TestRemoteMethod("test", ActionName.name("test"), "Test",
"A test method", EnumerableTargetObjectSchema.VOID.getName(),
new TestRemoteParameter("obj", EnumerableTargetObjectSchema.OBJECT.getName(), true,
null, "Arg1", "An argument"));
createTrace();
TraceObject root;
try (Transaction tx = tb.startTransaction()) {
TraceObjectValue rv = tb.trace.getObjectManager()
.createRootObject(CTX.getSchema(new SchemaName("Session")));
root = rv.getChild();
}
method.validate(Map.of("obj", root));
}
@Test
public void testRemoteMethodValidateObjectGivenProcess() throws Throwable {
RemoteMethod method = new TestRemoteMethod("test", ActionName.name("test"), "Test",
"A test method", EnumerableTargetObjectSchema.VOID.getName(),
new TestRemoteParameter("obj", EnumerableTargetObjectSchema.OBJECT.getName(), true,
null, "Arg1", "An argument"));
createTrace();
TraceObject process;
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(CTX.getSchema(new SchemaName("Session")));
process =
tb.trace.getObjectManager().createObject(TraceObjectKeyPath.parse("Processes[0]"));
process.insert(Lifespan.nowOn(0), ConflictResolution.DENY);
}
method.validate(Map.of("obj", process));
}
@Test(expected = IllegalArgumentException.class)
public void testRemoteMethodValidateObjectGivenInt() throws Throwable {
RemoteMethod method = new TestRemoteMethod("test", ActionName.name("test"), "Test",
"A test method", EnumerableTargetObjectSchema.VOID.getName(),
new TestRemoteParameter("obj", EnumerableTargetObjectSchema.OBJECT.getName(), true,
null, "Arg1", "An argument"));
method.validate(Map.of("obj", 1));
}
@Test
public void testRemoteMethodValidateProcessGivenProcess() throws Throwable {
RemoteMethod method = new TestRemoteMethod("test", ActionName.name("test"), "Test",
"A test method", EnumerableTargetObjectSchema.VOID.getName(),
new TestRemoteParameter("proc", new SchemaName("Process"), true,
null, "Proc1", "A Process argument"));
createTrace();
TraceObject process;
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(CTX.getSchema(new SchemaName("Session")));
process =
tb.trace.getObjectManager().createObject(TraceObjectKeyPath.parse("Processes[0]"));
process.insert(Lifespan.nowOn(0), ConflictResolution.DENY);
}
method.validate(Map.of("proc", process));
}
@Test(expected = IllegalArgumentException.class)
public void testRemoteMethodValidateProcessGivenInt() throws Throwable {
RemoteMethod method = new TestRemoteMethod("test", ActionName.name("test"), "Test",
"A test method", EnumerableTargetObjectSchema.VOID.getName(),
new TestRemoteParameter("proc", new SchemaName("Process"), true,
null, "Proc1", "A Process argument"));
// Otherwise "Process" schema doesn't exist
createTrace();
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(CTX.getSchema(new SchemaName("Session")));
}
method.validate(Map.of("proc", 1));
}
@Test
public void testRemoteMethodValidateAnyGivenInteger() throws Throwable {
RemoteMethod method = new TestRemoteMethod("test", ActionName.name("test"), "Test",
"A test method", EnumerableTargetObjectSchema.VOID.getName(),
new TestRemoteParameter("arg", EnumerableTargetObjectSchema.ANY.getName(), true,
null, "Arg1", "An argument"));
method.validate(Map.of("arg", 1));
}
@Test
public void testRemoteMethodValidateAnyGivenObject() throws Throwable {
RemoteMethod method = new TestRemoteMethod("test", ActionName.name("test"), "Test",
"A test method", EnumerableTargetObjectSchema.VOID.getName(),
new TestRemoteParameter("arg", EnumerableTargetObjectSchema.ANY.getName(), true,
null, "Arg1", "An argument"));
createTrace();
TraceObject root;
try (Transaction tx = tb.startTransaction()) {
TraceObjectValue rv = tb.trace.getObjectManager()
.createRootObject(CTX.getSchema(new SchemaName("Session")));
root = rv.getChild();
}
method.validate(Map.of("arg", root));
}
@Test
public void testRemoteMethodValidateAnyGivenProcess() throws Throwable {
RemoteMethod method = new TestRemoteMethod("test", ActionName.name("test"), "Test",
"A test method", EnumerableTargetObjectSchema.VOID.getName(),
new TestRemoteParameter("arg", EnumerableTargetObjectSchema.ANY.getName(), true,
null, "Arg1", "An argument"));
createTrace();
TraceObject process;
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(CTX.getSchema(new SchemaName("Session")));
process =
tb.trace.getObjectManager().createObject(TraceObjectKeyPath.parse("Processes[0]"));
process.insert(Lifespan.nowOn(0), ConflictResolution.DENY);
}
method.validate(Map.of("arg", process));
}
@Test
public void testRemoteMethodValidateIntegerGivenInteger() throws Throwable {
RemoteMethod method = new TestRemoteMethod("test", ActionName.name("test"), "Test",
"A test method", EnumerableTargetObjectSchema.VOID.getName(),
new TestRemoteParameter("arg", EnumerableTargetObjectSchema.INT.getName(), true,
null, "Arg1", "An argument"));
method.validate(Map.of("arg", 1));
}
@Test(expected = IllegalArgumentException.class)
public void testRemoteMethodValidateIntegerGivenLong() throws Throwable {
RemoteMethod method = new TestRemoteMethod("test", ActionName.name("test"), "Test",
"A test method", EnumerableTargetObjectSchema.VOID.getName(),
new TestRemoteParameter("arg", EnumerableTargetObjectSchema.INT.getName(), true,
null, "Arg1", "An argument"));
method.validate(Map.of("arg", 1L));
}
}

View file

@ -51,7 +51,7 @@ import ghidra.trace.model.thread.TraceThread;
public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerTest { public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerTest {
protected static final SchemaContext CTX; public static final SchemaContext CTX;
static { static {
try { try {

View file

@ -46,6 +46,11 @@ public enum EnumerableTargetObjectSchema implements TargetObjectSchema {
public AttributeSchema getDefaultAttributeSchema() { public AttributeSchema getDefaultAttributeSchema() {
return AttributeSchema.DEFAULT_ANY; return AttributeSchema.DEFAULT_ANY;
} }
@Override
public boolean isAssignableFrom(TargetObjectSchema that) {
return true;
}
}, },
/** /**
* The least restrictive, but least informative object schema. * The least restrictive, but least informative object schema.
@ -63,6 +68,12 @@ public enum EnumerableTargetObjectSchema implements TargetObjectSchema {
public AttributeSchema getDefaultAttributeSchema() { public AttributeSchema getDefaultAttributeSchema() {
return AttributeSchema.DEFAULT_ANY; return AttributeSchema.DEFAULT_ANY;
} }
@Override
public boolean isAssignableFrom(TargetObjectSchema that) {
// That is has as schema implies it's a TargetObject
return true;
}
}, },
TYPE(Class.class), TYPE(Class.class),
/** /**

View file

@ -1233,4 +1233,20 @@ public interface TargetObjectSchema {
} }
throw new IllegalArgumentException("No index between stack and frame"); throw new IllegalArgumentException("No index between stack and frame");
} }
/**
* Check if this schema can accept a value of the given other schema
*
* <p>
* This works analogously to {@link Class#isAssignableFrom(Class)}, except that schemas are
* quite a bit less flexible. Only {@link EnumerableTargetObjectSchema#ANY} and
* {@link EnumerableTargetObjectSchema#OBJECT} can accept anything other than exactly
* themselves.
*
* @param that
* @return true if an object of that schema can be assigned to this schema.
*/
default boolean isAssignableFrom(TargetObjectSchema that) {
return this.equals(that);
}
} }