mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
Fixing several debugger-related tests
This commit is contained in:
parent
4bb7cfaa71
commit
c481a87ab5
19 changed files with 214 additions and 79 deletions
|
@ -21,7 +21,6 @@ import static org.junit.Assert.assertTrue;
|
|||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
|
@ -136,7 +135,8 @@ public class PtyTest {
|
|||
PtyMaster master = pty.getMaster();
|
||||
PrintWriter writer = new PrintWriter(master.getOutputStream());
|
||||
BufferedReader reader = loggingReader(master.getInputStream());
|
||||
Process bash = pty.getSlave().session(new String[] { DummyProc.which("bash") }, env);
|
||||
Process bash =
|
||||
pty.getSlave().session(new String[] { DummyProc.which("bash"), "--norc" }, env);
|
||||
runExitCheck(3, bash);
|
||||
|
||||
writer.println("echo test");
|
||||
|
@ -150,14 +150,15 @@ public class PtyTest {
|
|||
writer.println("exit 3");
|
||||
writer.flush();
|
||||
|
||||
assertTrue(Set.of("BASH:exit 3", "exit 3").contains(reader.readLine()));
|
||||
line = reader.readLine();
|
||||
assertTrue("Not 'exit 3' or 'BASH:exit 3': '" + line + "'",
|
||||
Set.of("BASH:exit 3", "exit 3").contains(line));
|
||||
|
||||
assertEquals(3, bash.waitFor());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Some extra bash kruft is sneaking in, and I don't know how")
|
||||
public void testSessionBashInterruptCat() throws IOException, InterruptedException {
|
||||
Map<String, String> env = new HashMap<>();
|
||||
env.put("PS1", "BASH:");
|
||||
|
@ -165,7 +166,8 @@ public class PtyTest {
|
|||
PtyMaster master = pty.getMaster();
|
||||
PrintWriter writer = new PrintWriter(master.getOutputStream());
|
||||
BufferedReader reader = loggingReader(master.getInputStream());
|
||||
Process bash = pty.getSlave().session(new String[] { DummyProc.which("bash") }, env);
|
||||
Process bash =
|
||||
pty.getSlave().session(new String[] { DummyProc.which("bash"), "--norc" }, env);
|
||||
runExitCheck(3, bash);
|
||||
|
||||
writer.println("echo test");
|
||||
|
@ -178,7 +180,9 @@ public class PtyTest {
|
|||
|
||||
writer.println("cat");
|
||||
writer.flush();
|
||||
assertTrue(Set.of("BASH:cat", "cat").contains(reader.readLine()));
|
||||
line = reader.readLine();
|
||||
assertTrue("Not 'cat' or 'BASH:cat': '" + line + "'",
|
||||
Set.of("BASH:cat", "cat").contains(line));
|
||||
|
||||
writer.println("Hello, cat!");
|
||||
writer.flush();
|
||||
|
|
|
@ -141,10 +141,14 @@ public class GadpClientTest {
|
|||
.setSequence(seqno)
|
||||
.setConnectRequest(GadpVersion.makeRequest())
|
||||
.build());
|
||||
// TODO: Test schemas here?
|
||||
// Seems they're already hit well enough in dependent tests
|
||||
send(Gadp.RootMessage.newBuilder()
|
||||
.setSequence(seqno)
|
||||
.setConnectReply(Gadp.ConnectReply.newBuilder()
|
||||
.setVersion(version.getName()))
|
||||
.setVersion(version.getName())
|
||||
.setSchemaContext("<context/>")
|
||||
.setRootSchema("OBJECT"))
|
||||
.build());
|
||||
seqno++;
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
|||
|
||||
@Override
|
||||
public void displayChanged(TargetObject object, String display) {
|
||||
// TODO: Would rather update the sub-title
|
||||
guiConsole.updateTitle();
|
||||
}
|
||||
|
||||
|
@ -121,6 +122,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
|||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
// This is kruft. Would be better to have control of the sub-title.
|
||||
if (firstTimeAskedTitle) {
|
||||
firstTimeAskedTitle = false;
|
||||
return plugin.getName();
|
||||
|
|
|
@ -19,6 +19,8 @@ import java.io.IOException;
|
|||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.ConnectException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import docking.ActionContext;
|
||||
|
@ -33,6 +35,7 @@ import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
|
|||
import ghidra.app.services.*;
|
||||
import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
|
||||
import ghidra.async.AsyncReference;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.attributes.TargetObjectRef;
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.dbg.target.TargetThread;
|
||||
|
@ -61,25 +64,25 @@ import ghidra.util.exception.*;
|
|||
import ghidra.util.task.*;
|
||||
|
||||
@PluginInfo( //
|
||||
shortDescription = "Debugger Trace View Management Plugin", //
|
||||
description = "Manages UI Components, Wrappers, Focus, etc.", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsProduced = { //
|
||||
TraceActivatedPluginEvent.class, //
|
||||
}, //
|
||||
eventsConsumed = { //
|
||||
TraceActivatedPluginEvent.class, //
|
||||
TraceClosedPluginEvent.class, //
|
||||
ModelObjectFocusedPluginEvent.class, //
|
||||
TraceRecorderAdvancedPluginEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
}, //
|
||||
servicesProvided = { //
|
||||
DebuggerTraceManagerService.class, //
|
||||
} //
|
||||
shortDescription = "Debugger Trace View Management Plugin", //
|
||||
description = "Manages UI Components, Wrappers, Focus, etc.", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsProduced = { //
|
||||
TraceActivatedPluginEvent.class, //
|
||||
}, //
|
||||
eventsConsumed = { //
|
||||
TraceActivatedPluginEvent.class, //
|
||||
TraceClosedPluginEvent.class, //
|
||||
ModelObjectFocusedPluginEvent.class, //
|
||||
TraceRecorderAdvancedPluginEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
}, //
|
||||
servicesProvided = { //
|
||||
DebuggerTraceManagerService.class, //
|
||||
} //
|
||||
)
|
||||
public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
implements DebuggerTraceManagerService {
|
||||
|
@ -127,21 +130,29 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
class ForRecordersListener implements CollectionChangeListener<TraceRecorder> {
|
||||
@Override
|
||||
public void elementAdded(TraceRecorder recorder) {
|
||||
updateCurrentRecorder();
|
||||
Swing.runLater(() -> updateCurrentRecorder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementRemoved(TraceRecorder recorder) {
|
||||
if (isAutoCloseOnTerminate()) {
|
||||
Trace trace = recorder.getTrace();
|
||||
if (getOpenTraces().contains(trace)) {
|
||||
if (isSaveTracesByDefault()) {
|
||||
saveTrace(trace);
|
||||
}
|
||||
closeTrace(trace);
|
||||
Swing.runLater(() -> {
|
||||
updateCurrentRecorder();
|
||||
if (!isAutoCloseOnTerminate()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateCurrentRecorder();
|
||||
Trace trace = recorder.getTrace();
|
||||
synchronized (listenersByTrace) {
|
||||
if (!listenersByTrace.containsKey(trace)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!isSaveTracesByDefault()) {
|
||||
closeTrace(trace);
|
||||
return;
|
||||
}
|
||||
// Errors already handled by saveTrace
|
||||
tryHarder(() -> saveTrace(trace), 3, 100).thenRun(() -> closeTrace(trace));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,6 +201,22 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
return t;
|
||||
}
|
||||
|
||||
protected <T> CompletableFuture<T> tryHarder(Supplier<CompletableFuture<T>> action,
|
||||
int retries, long retryAfterMillis) {
|
||||
Executor exe = CompletableFuture.delayedExecutor(retryAfterMillis, TimeUnit.MILLISECONDS);
|
||||
// NB. thenCompose(f -> f) also ensures exceptions are handled here, not passed through
|
||||
CompletableFuture<T> result =
|
||||
CompletableFuture.supplyAsync(action, AsyncUtils.SWING_EXECUTOR).thenCompose(f -> f);
|
||||
if (retries > 0) {
|
||||
return result.thenApply(CompletableFuture::completedFuture).exceptionally(ex -> {
|
||||
return CompletableFuture
|
||||
.supplyAsync(() -> tryHarder(action, retries - 1, retryAfterMillis), exe)
|
||||
.thenCompose(f -> f);
|
||||
}).thenCompose(f -> f);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
@ -746,8 +773,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
}
|
||||
}
|
||||
|
||||
public static void saveTrace(PluginTool tool, Trace trace) {
|
||||
public static CompletableFuture<Void> saveTrace(PluginTool tool, Trace trace) {
|
||||
tool.prepareToSave(trace);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
// TODO: Get all the nuances for this correct...
|
||||
// "Save As" action, Locking, transaction flushing, etc....
|
||||
if (trace.getDomainFile().getParent() != null) {
|
||||
|
@ -756,17 +784,24 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
try {
|
||||
trace.getDomainFile().save(monitor);
|
||||
future.complete(null);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// Done
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (NotConnectedException | ConnectException e) {
|
||||
ClientUtil.promptForReconnect(tool.getProject().getRepository(),
|
||||
tool.getToolFrame());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
ClientUtil.handleException(tool.getProject().getRepository(), e,
|
||||
"Save Trace", tool.getToolFrame());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -795,34 +830,41 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
try {
|
||||
traces.createFile(finalFilename, trace, monitor);
|
||||
trace.save("Initial save", monitor);
|
||||
future.complete(null);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// Done
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (NotConnectedException | ConnectException e) {
|
||||
ClientUtil.promptForReconnect(tool.getProject().getRepository(),
|
||||
tool.getToolFrame());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
ClientUtil.handleException(tool.getProject().getRepository(), e,
|
||||
"Save New Trace", tool.getToolFrame());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
Msg.showError(DebuggerTraceManagerServicePlugin.class, null,
|
||||
"Save New Trace Error", e.getMessage());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.showError(DebuggerTraceManagerServicePlugin.class, null,
|
||||
"Save New Trace Error", e.getMessage(), e);
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveTrace(Trace trace) {
|
||||
saveTrace(tool, trace);
|
||||
public CompletableFuture<Void> saveTrace(Trace trace) {
|
||||
return saveTrace(tool, trace);
|
||||
}
|
||||
|
||||
protected void doTraceClosed(Trace trace) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.app.services;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||
|
@ -92,14 +93,17 @@ public interface DebuggerTraceManagerService {
|
|||
*
|
||||
* <p>
|
||||
* If a different domain file of the trace's name already exists, an incrementing integer is
|
||||
* appended.
|
||||
* appended. Errors are handled in the same fashion as saving a program, so there is little/no
|
||||
* need to invoke {@link CompletableFuture#exceptionally(java.util.function.Function)} on the
|
||||
* returned future. The future is returned as a means of registering follow-up actions.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Support save-as, prompting to overwrite, etc?
|
||||
*
|
||||
* @param trace the trace to save
|
||||
* @return a future which completes when the save is finished
|
||||
*/
|
||||
void saveTrace(Trace trace);
|
||||
CompletableFuture<Void> saveTrace(Trace trace);
|
||||
|
||||
void closeTrace(Trace trace);
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@ public class DebuggerInterpreterPluginTest extends AbstractGhidraHeadedDebuggerG
|
|||
InterpreterComponentProvider interpreter =
|
||||
waitForComponentProvider(InterpreterComponentProvider.class);
|
||||
|
||||
assertEquals("Interpreter: Test Debugger", interpreter.getTitle());
|
||||
// TODO: Sub-title instead
|
||||
assertEquals("Test Debugger", interpreter.getTitle());
|
||||
assertTrue(interpreter.isVisible());
|
||||
}
|
||||
|
||||
|
@ -83,7 +84,8 @@ public class DebuggerInterpreterPluginTest extends AbstractGhidraHeadedDebuggerG
|
|||
waitForSwing();
|
||||
|
||||
// I/O processing has a dedicated thread
|
||||
waitForPass(() -> assertEquals("Hello, World!\n", interpreter.getOutputText()));
|
||||
// FIXME: The trailing space is a hack to fix scrolling....
|
||||
waitForPass(() -> assertEquals("Hello, World!\n ", interpreter.getOutputText()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -109,7 +111,8 @@ public class DebuggerInterpreterPluginTest extends AbstractGhidraHeadedDebuggerG
|
|||
mb.testModel.session.interpreter.setDisplay("Test Debugger X.0");
|
||||
waitForSwing();
|
||||
|
||||
assertEquals("Interpreter: Test Debugger X.0", interpreter.getTitle());
|
||||
// TODO: Sub-title instead
|
||||
assertEquals("Test Debugger X.0", interpreter.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -664,6 +664,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
assertArrayEquals(zero, buf.array());
|
||||
|
||||
runSwing(() -> goToDyn(addr(trace, 0x55551800)));
|
||||
waitForPass(() -> assertEquals(addr(trace, 0x55551800),
|
||||
listingProvider.getLocation().getAddress()));
|
||||
waitForDomainObject(trace);
|
||||
buf.clear();
|
||||
assertEquals(data.length,
|
||||
|
|
|
@ -20,6 +20,7 @@ import static org.junit.Assert.*;
|
|||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.junit.*;
|
||||
|
||||
import generic.Unique;
|
||||
|
@ -158,7 +159,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals("{ de ad be ef }", row.getRawValueString());
|
||||
assertEquals("0xdeadbeef", row.getRawValueString());
|
||||
assertEquals("DEADBEEFh", row.getValueString());
|
||||
assertNoErr(row);
|
||||
|
||||
|
@ -180,7 +181,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals("{ 00 00 00 00 00 40 00 08 }", row.getRawValueString());
|
||||
assertEquals("0x400008", row.getRawValueString());
|
||||
assertEquals("400008h", row.getValueString());
|
||||
assertNoErr(row);
|
||||
|
||||
|
@ -226,6 +227,9 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
row.setDataType(LongDataType.dataType);
|
||||
|
||||
waitForPass(() -> {
|
||||
if (row.getError() != null) {
|
||||
ExceptionUtils.rethrow(row.getError());
|
||||
}
|
||||
assertEquals("{ 01 02 03 04 }", row.getRawValueString());
|
||||
assertEquals("1020304h", row.getValueString());
|
||||
});
|
||||
|
|
|
@ -60,6 +60,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
/**
|
||||
* Tracks the current set of logical breakpoints.
|
||||
*
|
||||
* <p>
|
||||
* Its assertions require perfection in the sequence of events: 1) No double-adds. 2) No
|
||||
* double-removes. 3) No extraneous updates. At the end of each test, the current set of
|
||||
* breakpoints in this listener should be verified against those reported by the service.
|
||||
|
@ -1057,17 +1058,20 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
addTargetSoftwareBreakpoint(recorder3, text3);
|
||||
waitForSwing();
|
||||
|
||||
assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3);
|
||||
waitForPass(
|
||||
() -> assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3));
|
||||
|
||||
expectMappingChange(() -> {
|
||||
// TODO: Change breakpoint manager to require both open and recording...
|
||||
// If I don't close the trace here, the test will fail.
|
||||
recorder3.stopRecording();
|
||||
traceManager.closeTrace(trace3);
|
||||
// NB. Auto-close on stop is the default
|
||||
//traceManager.closeTrace(trace3);
|
||||
});
|
||||
waitForSwing();
|
||||
|
||||
assertLogicalBreakpointForMappedBookmarkAnd1TraceBreakpoint(trace1);
|
||||
// NB. Auto-close is possibly delayed because of auto-save
|
||||
waitForPass(() -> assertLogicalBreakpointForMappedBookmarkAnd1TraceBreakpoint(trace1));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1318,7 +1322,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
addTargetSoftwareBreakpoint(recorder1, text);
|
||||
waitForDomainObject(trace);
|
||||
|
||||
assertLogicalBreakpointForLoneSoftwareBreakpoint(trace);
|
||||
waitForPass(() -> assertLogicalBreakpointForLoneSoftwareBreakpoint(trace));
|
||||
|
||||
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
|
||||
|
||||
|
|
|
@ -462,6 +462,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
|
|||
new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).size());
|
||||
|
||||
traceManager.closeTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(mappingService.getOpenMappedLocations(
|
||||
new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).isEmpty());
|
||||
|
@ -490,11 +491,14 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
|
|||
throws Exception {
|
||||
addMapping();
|
||||
traceManager.closeTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
// pre-check
|
||||
assertTrue(mappingService.getOpenMappedLocations(
|
||||
new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).isEmpty());
|
||||
|
||||
traceManager.openTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(1, mappingService.getOpenMappedLocations(
|
||||
new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).size());
|
||||
|
|
|
@ -205,7 +205,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
|||
waitForDomainObject(tb.trace);
|
||||
|
||||
assertEquals(Set.of(), traceManager.getOpenTraces());
|
||||
assertEquals(Set.of(tb), tb.trace.getConsumerList());
|
||||
assertEquals(Set.of(tb), Set.copyOf(tb.trace.getConsumerList()));
|
||||
|
||||
traceManager.openTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
|
|
@ -29,6 +29,7 @@ import ghidra.dbg.attributes.TargetObjectRef;
|
|||
import ghidra.dbg.attributes.TypedTargetObjectRef;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
|
||||
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema.MinimalSchemaContext;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.util.Msg;
|
||||
|
@ -207,8 +208,19 @@ public class AnnotatedSchemaContext extends DefaultSchemaContext {
|
|||
protected TargetObjectSchema fromAnnotatedClass(Class<? extends TargetObject> cls) {
|
||||
synchronized (namesByClass) {
|
||||
SchemaName name = nameFromAnnotatedClass(cls);
|
||||
TargetObjectSchema enumerable = MinimalSchemaContext.INSTANCE.getSchemaOrNull(name);
|
||||
if (enumerable != null) {
|
||||
throw new IllegalArgumentException("Class " + cls + " is assigned name " + name +
|
||||
". This usually means it's missing the @" +
|
||||
TargetObjectSchemaInfo.class.getSimpleName() +
|
||||
" annotation, or that the class was referenced by accident.");
|
||||
}
|
||||
return schemasByClass.computeIfAbsent(cls, c -> {
|
||||
TargetObjectSchemaInfo info = cls.getAnnotation(TargetObjectSchemaInfo.class);
|
||||
if (info == null) {
|
||||
throw new IllegalArgumentException("Class " + cls + " is not annotated with @" +
|
||||
TargetObjectSchemaInfo.class.getSimpleName());
|
||||
}
|
||||
SchemaBuilder builder = builder(name);
|
||||
|
||||
Set<Class<?>> allParents = ReflectionUtilities.getAllParents(cls);
|
||||
|
|
|
@ -39,6 +39,11 @@ public class DefaultSchemaContext implements SchemaContext {
|
|||
schemas.put(schema.getName(), schema);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized TargetObjectSchema getSchemaOrNull(SchemaName name) {
|
||||
return schemas.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized TargetObjectSchema getSchema(SchemaName name) {
|
||||
return Objects.requireNonNull(schemas.get(name), "No such schema name: " + name);
|
||||
|
|
|
@ -93,8 +93,8 @@ public enum EnumerableTargetObjectSchema implements TargetObjectSchema {
|
|||
EXECUTION_STATE("EXECUTION_STATE", TargetExecutionState.class),
|
||||
UPDATE_MODE("UPDATE_MODE", TargetUpdateMode.class);
|
||||
|
||||
private static class MinimalSchemaContext extends DefaultSchemaContext {
|
||||
private static final SchemaContext INSTANCE = new MinimalSchemaContext();
|
||||
public static final class MinimalSchemaContext extends DefaultSchemaContext {
|
||||
public static final SchemaContext INSTANCE = new MinimalSchemaContext();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,6 +37,14 @@ public interface SchemaContext {
|
|||
*/
|
||||
TargetObjectSchema getSchema(SchemaName name);
|
||||
|
||||
/**
|
||||
* Resolve a schema in this context by name
|
||||
*
|
||||
* @param name the schema's name
|
||||
* @return the schema, or null if no schema by the given name exists
|
||||
*/
|
||||
TargetObjectSchema getSchemaOrNull(SchemaName name);
|
||||
|
||||
/**
|
||||
* Collect all schemas in this context
|
||||
*
|
||||
|
|
|
@ -31,6 +31,26 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
|||
|
||||
public class AnnotatedTargetObjectSchemaTest {
|
||||
|
||||
protected static SchemaBuilder addBasicAttributes(SchemaBuilder builder) {
|
||||
builder.addAttributeSchema(new DefaultAttributeSchema("_value",
|
||||
EnumerableTargetObjectSchema.ANY.getName(), false, false, true), null);
|
||||
builder.addAttributeSchema(new DefaultAttributeSchema("_type",
|
||||
EnumerableTargetObjectSchema.STRING.getName(), false, false, true), null);
|
||||
builder.addAttributeSchema(new DefaultAttributeSchema("_display",
|
||||
EnumerableTargetObjectSchema.STRING.getName(), false, false, true), null);
|
||||
builder.addAttributeSchema(new DefaultAttributeSchema("_short_display",
|
||||
EnumerableTargetObjectSchema.STRING.getName(), false, false, true), null);
|
||||
builder.addAttributeSchema(new DefaultAttributeSchema("_kind",
|
||||
EnumerableTargetObjectSchema.STRING.getName(), false, true, true), null);
|
||||
builder.addAttributeSchema(new DefaultAttributeSchema("_update_mode",
|
||||
EnumerableTargetObjectSchema.UPDATE_MODE.getName(), false, false, true), null);
|
||||
builder.addAttributeSchema(new DefaultAttributeSchema("_order",
|
||||
EnumerableTargetObjectSchema.INT.getName(), false, false, true), null);
|
||||
builder.addAttributeSchema(new DefaultAttributeSchema("_modified",
|
||||
EnumerableTargetObjectSchema.BOOL.getName(), false, false, true), null);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@TargetObjectSchemaInfo
|
||||
static class TestAnnotatedTargetRootPlain extends DefaultTargetModelRoot {
|
||||
public TestAnnotatedTargetRootPlain(DebuggerObjectModel model, String typeHint) {
|
||||
|
@ -43,7 +63,7 @@ public class AnnotatedTargetObjectSchemaTest {
|
|||
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
|
||||
TargetObjectSchema schema = ctx.getSchemaForClass(TestAnnotatedTargetRootPlain.class);
|
||||
|
||||
TargetObjectSchema exp = ctx.builder(schema.getName())
|
||||
TargetObjectSchema exp = addBasicAttributes(ctx.builder(schema.getName()))
|
||||
.addInterface(TargetAggregate.class) // Inherited from root
|
||||
.build();
|
||||
assertEquals(exp, schema);
|
||||
|
@ -61,7 +81,7 @@ public class AnnotatedTargetObjectSchemaTest {
|
|||
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
|
||||
TargetObjectSchema schema = ctx.getSchemaForClass(TestAnnotatedTargetRootNoElems.class);
|
||||
|
||||
TargetObjectSchema exp = ctx.builder(schema.getName())
|
||||
TargetObjectSchema exp = addBasicAttributes(ctx.builder(schema.getName()))
|
||||
.addInterface(TargetAggregate.class) // Inherited from root
|
||||
.setDefaultElementSchema(EnumerableTargetObjectSchema.VOID.getName())
|
||||
.build();
|
||||
|
@ -100,7 +120,7 @@ public class AnnotatedTargetObjectSchemaTest {
|
|||
|
||||
SchemaName schemaProc = ctx.nameFromClass(TestAnnotatedTargetProcessStub.class);
|
||||
|
||||
TargetObjectSchema exp = ctx.builder(schema.getName())
|
||||
TargetObjectSchema exp = addBasicAttributes(ctx.builder(schema.getName()))
|
||||
.addInterface(TargetAggregate.class)
|
||||
.setDefaultElementSchema(schemaProc)
|
||||
.build();
|
||||
|
@ -124,14 +144,8 @@ public class AnnotatedTargetObjectSchemaTest {
|
|||
|
||||
SchemaName schemaProc = ctx.nameFromClass(TestAnnotatedTargetProcessStub.class);
|
||||
|
||||
TargetObjectSchema exp = ctx.builder(schema.getName())
|
||||
TargetObjectSchema exp = addBasicAttributes(ctx.builder(schema.getName()))
|
||||
.setDefaultElementSchema(schemaProc)
|
||||
.addAttributeSchema(new DefaultAttributeSchema("_display",
|
||||
EnumerableTargetObjectSchema.STRING.getName(), false, false, true), null)
|
||||
.addAttributeSchema(new DefaultAttributeSchema("_short_display",
|
||||
EnumerableTargetObjectSchema.STRING.getName(), false, false, true), null)
|
||||
.addAttributeSchema(new DefaultAttributeSchema("_update_mode",
|
||||
EnumerableTargetObjectSchema.UPDATE_MODE.getName(), false, false, true), null)
|
||||
.build();
|
||||
assertEquals(exp, schema);
|
||||
}
|
||||
|
@ -172,7 +186,7 @@ public class AnnotatedTargetObjectSchemaTest {
|
|||
|
||||
SchemaName schemaProc = ctx.nameFromClass(TestAnnotatedTargetProcessParam.class);
|
||||
|
||||
TargetObjectSchema exp = ctx.builder(schema.getName())
|
||||
TargetObjectSchema exp = addBasicAttributes(ctx.builder(schema.getName()))
|
||||
.addInterface(TargetAggregate.class)
|
||||
.addAttributeSchema(new DefaultAttributeSchema("int_attribute",
|
||||
EnumerableTargetObjectSchema.INT.getName(), false, false, false), null)
|
||||
|
@ -183,13 +197,17 @@ public class AnnotatedTargetObjectSchemaTest {
|
|||
assertEquals("TestAnnotatedTargetRootWithAnnotatedAttrs", schema.getName().toString());
|
||||
}
|
||||
|
||||
@TargetObjectSchemaInfo(attributes = {
|
||||
@TargetAttributeType(type = Void.class),
|
||||
@TargetAttributeType(name = "some_int_attribute", type = Integer.class),
|
||||
@TargetAttributeType(name = "some_object_attribute", type = TestAnnotatedTargetProcessStub.class)
|
||||
}, elements = {
|
||||
@TargetElementType(index = "reserved", type = Void.class)
|
||||
})
|
||||
@TargetObjectSchemaInfo(
|
||||
attributes = {
|
||||
@TargetAttributeType(type = Void.class),
|
||||
@TargetAttributeType(name = "some_int_attribute", type = Integer.class),
|
||||
@TargetAttributeType(
|
||||
name = "some_object_attribute",
|
||||
type = TestAnnotatedTargetProcessStub.class)
|
||||
},
|
||||
elements = {
|
||||
@TargetElementType(index = "reserved", type = Void.class)
|
||||
})
|
||||
static class TestAnnotatedTargetRootWithListedAttrs extends DefaultTargetModelRoot {
|
||||
public TestAnnotatedTargetRootWithListedAttrs(DebuggerObjectModel model,
|
||||
String typeHint) {
|
||||
|
@ -205,7 +223,7 @@ public class AnnotatedTargetObjectSchemaTest {
|
|||
|
||||
SchemaName schemaProc = ctx.nameFromClass(TestAnnotatedTargetProcessStub.class);
|
||||
|
||||
TargetObjectSchema exp = ctx.builder(schema.getName())
|
||||
TargetObjectSchema exp = addBasicAttributes(ctx.builder(schema.getName()))
|
||||
.addInterface(TargetAggregate.class)
|
||||
.setDefaultAttributeSchema(new DefaultAttributeSchema("",
|
||||
EnumerableTargetObjectSchema.VOID.getName(), false, false, false))
|
||||
|
@ -248,8 +266,11 @@ public class AnnotatedTargetObjectSchemaTest {
|
|||
ctx.getSchemaForClass(DefaultTargetObject.class);
|
||||
}
|
||||
|
||||
static class Dummy {
|
||||
}
|
||||
|
||||
@TargetObjectSchemaInfo
|
||||
static class TestAnnotatedTargetRootWithAnnotatedAttrsNonUnique<T extends TargetProcess<T> & TargetInterpreter<T>>
|
||||
static class TestAnnotatedTargetRootWithAnnotatedAttrsNonUnique<T extends Dummy & TargetProcess<T> & TargetInterpreter<T>>
|
||||
extends DefaultTargetModelRoot {
|
||||
|
||||
public TestAnnotatedTargetRootWithAnnotatedAttrsNonUnique(DebuggerObjectModel model,
|
||||
|
@ -270,7 +291,7 @@ public class AnnotatedTargetObjectSchemaTest {
|
|||
}
|
||||
|
||||
@TargetObjectSchemaInfo
|
||||
static class TestAnnotatedTargetRootWithElemsNonUnique<T extends TargetProcess<T> & TargetInterpreter<T>>
|
||||
static class TestAnnotatedTargetRootWithElemsNonUnique<T extends Dummy & TargetProcess<T> & TargetInterpreter<T>>
|
||||
extends DefaultTargetModelRoot {
|
||||
|
||||
public TestAnnotatedTargetRootWithElemsNonUnique(DebuggerObjectModel model,
|
||||
|
@ -329,7 +350,8 @@ public class AnnotatedTargetObjectSchemaTest {
|
|||
ctx.getSchemaForClass(TestAnnotatedTargetRootWithAnnotatedAttrsBadGetter.class);
|
||||
}
|
||||
|
||||
@TargetObjectSchemaInfo(attributes = @TargetAttributeType(name = "some_attr", type = NotAPrimitive.class))
|
||||
@TargetObjectSchemaInfo(
|
||||
attributes = @TargetAttributeType(name = "some_attr", type = NotAPrimitive.class))
|
||||
static class TestAnnotatedTargetRootWithListedAttrsBadType extends DefaultTargetModelRoot {
|
||||
public TestAnnotatedTargetRootWithListedAttrsBadType(DebuggerObjectModel model,
|
||||
String typeHint) {
|
||||
|
|
|
@ -26,8 +26,20 @@ import ghidra.util.database.spatial.rect.EuclideanSpace2D;
|
|||
public class TraceAddressSnapSpace implements EuclideanSpace2D<Address, Long> {
|
||||
private static final Map<AddressSpace, TraceAddressSnapSpace> SPACES = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Get the trace-address-snap space for a given address space
|
||||
*
|
||||
* <p>
|
||||
* Because this synchronizes on a cache of spaces, it should only be called by space
|
||||
* constructors, never by entry constructors.
|
||||
*
|
||||
* @param space the address space
|
||||
* @return the trace-address-snap space
|
||||
*/
|
||||
public static TraceAddressSnapSpace forAddressSpace(AddressSpace space) {
|
||||
return SPACES.computeIfAbsent(space, TraceAddressSnapSpace::new);
|
||||
synchronized (SPACES) {
|
||||
return SPACES.computeIfAbsent(space, TraceAddressSnapSpace::new);
|
||||
}
|
||||
}
|
||||
|
||||
private ImmutableTraceAddressSnapRange full;
|
||||
|
|
|
@ -558,7 +558,10 @@ public class DBCachedObjectStoreFactory {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends DBAnnotatedObject> TableInfo<T> getInfo(Class<T> cls) {
|
||||
return (TableInfo<T>) INFO_MAP.computeIfAbsent(cls, DBCachedObjectStoreFactory::buildInfo);
|
||||
synchronized (INFO_MAP) {
|
||||
return (TableInfo<T>) INFO_MAP.computeIfAbsent(cls,
|
||||
DBCachedObjectStoreFactory::buildInfo);
|
||||
}
|
||||
}
|
||||
|
||||
static <OT extends DBAnnotatedObject> List<DBFieldCodec<?, OT, ?>> getCodecs(
|
||||
|
|
|
@ -247,7 +247,7 @@ public abstract class AbstractConstraintsTree< //
|
|||
data.sort(Comparator.comparing(DR::getBounds, query.getBoundsComparator()));
|
||||
}
|
||||
for (DR d : data) {
|
||||
if (query != null && query.terminateEarlyData(d.getShape())) {
|
||||
if (query != null && ordered && query.terminateEarlyData(d.getShape())) {
|
||||
break;
|
||||
}
|
||||
boolean included = query == null || query.testData(d.getShape());
|
||||
|
@ -268,7 +268,7 @@ public abstract class AbstractConstraintsTree< //
|
|||
nodes.sort(Comparator.comparing(NR::getBounds, query.getBoundsComparator()));
|
||||
}
|
||||
for (NR n : nodes) {
|
||||
if (query != null && query.terminateEarlyNode(n.getShape())) {
|
||||
if (query != null && ordered && query.terminateEarlyNode(n.getShape())) {
|
||||
break;
|
||||
}
|
||||
r = visit(node, n, query, visitor, ordered);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue