diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/ffi/linux/PtyTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/ffi/linux/PtyTest.java index f0d7da4f6a..0fbbfd5253 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/ffi/linux/PtyTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/ffi/linux/PtyTest.java @@ -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 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(); diff --git a/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/client/GadpClientTest.java b/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/client/GadpClientTest.java index b8db71a685..d1818ffd2f 100644 --- a/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/client/GadpClientTest.java +++ b/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/client/GadpClientTest.java @@ -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("") + .setRootSchema("OBJECT")) .build()); seqno++; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/interpreters/AbstractDebuggerWrappedConsoleConnection.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/interpreters/AbstractDebuggerWrappedConsoleConnection.java index 1197898060..383e24703a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/interpreters/AbstractDebuggerWrappedConsoleConnection.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/interpreters/AbstractDebuggerWrappedConsoleConnection.java @@ -71,6 +71,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection { @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 CompletableFuture tryHarder(Supplier> 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 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 saveTrace(PluginTool tool, Trace trace) { tool.prepareToSave(trace); + CompletableFuture 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 saveTrace(Trace trace) { + return saveTrace(tool, trace); } protected void doTraceClosed(Trace trace) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java index fb4df12f24..44f1eebcbb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java @@ -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 { * *

* 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. * *

* 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 saveTrace(Trace trace); void closeTrace(Trace trace); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/interpreters/DebuggerInterpreterPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/interpreters/DebuggerInterpreterPluginTest.java index 708bb2f338..6b379a21f4 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/interpreters/DebuggerInterpreterPluginTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/interpreters/DebuggerInterpreterPluginTest.java @@ -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 diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index 05845f519e..82c60d6234 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -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, diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java index d3575a6e1a..3a1c87c5a8 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java @@ -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()); }); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java index 85b429f31a..a9b914bf12 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java @@ -60,6 +60,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe /** * Tracks the current set of logical breakpoints. * + *

* 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()); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java index 3efb9b4cf3..57e1d64e7f 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java @@ -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()); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java index 3f55015dce..f6f4b67c6e 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java @@ -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(); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/AnnotatedSchemaContext.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/AnnotatedSchemaContext.java index af4f85ab97..eb1826e34b 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/AnnotatedSchemaContext.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/AnnotatedSchemaContext.java @@ -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 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> allParents = ReflectionUtilities.getAllParents(cls); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/DefaultSchemaContext.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/DefaultSchemaContext.java index 29ac5847be..f4d02aa401 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/DefaultSchemaContext.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/DefaultSchemaContext.java @@ -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); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/EnumerableTargetObjectSchema.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/EnumerableTargetObjectSchema.java index b837c198b7..33f7f4a4fe 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/EnumerableTargetObjectSchema.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/EnumerableTargetObjectSchema.java @@ -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(); } /** diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/SchemaContext.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/SchemaContext.java index dad3893078..dd5fd2c9cd 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/SchemaContext.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/SchemaContext.java @@ -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 * diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/schema/AnnotatedTargetObjectSchemaTest.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/schema/AnnotatedTargetObjectSchemaTest.java index f69b7e72e6..17bc08f9ea 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/schema/AnnotatedTargetObjectSchemaTest.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/target/schema/AnnotatedTargetObjectSchemaTest.java @@ -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 & TargetInterpreter> + static class TestAnnotatedTargetRootWithAnnotatedAttrsNonUnique & TargetInterpreter> extends DefaultTargetModelRoot { public TestAnnotatedTargetRootWithAnnotatedAttrsNonUnique(DebuggerObjectModel model, @@ -270,7 +291,7 @@ public class AnnotatedTargetObjectSchemaTest { } @TargetObjectSchemaInfo - static class TestAnnotatedTargetRootWithElemsNonUnique & TargetInterpreter> + static class TestAnnotatedTargetRootWithElemsNonUnique & TargetInterpreter> 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) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceAddressSnapSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceAddressSnapSpace.java index 896ad8f438..8b42d6855b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceAddressSnapSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceAddressSnapSpace.java @@ -26,8 +26,20 @@ import ghidra.util.database.spatial.rect.EuclideanSpace2D; public class TraceAddressSnapSpace implements EuclideanSpace2D { private static final Map SPACES = new HashMap<>(); + /** + * Get the trace-address-snap space for a given address space + * + *

+ * 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; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java index efd7cdcc59..e131f0ecce 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java @@ -558,7 +558,10 @@ public class DBCachedObjectStoreFactory { @SuppressWarnings("unchecked") public static TableInfo getInfo(Class cls) { - return (TableInfo) INFO_MAP.computeIfAbsent(cls, DBCachedObjectStoreFactory::buildInfo); + synchronized (INFO_MAP) { + return (TableInfo) INFO_MAP.computeIfAbsent(cls, + DBCachedObjectStoreFactory::buildInfo); + } } static List> getCodecs( diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTree.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTree.java index d649767635..6ac0c9d4dc 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTree.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/AbstractConstraintsTree.java @@ -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);