Fixing several debugger-related tests

This commit is contained in:
Dan 2021-01-19 08:24:09 -05:00
parent 4bb7cfaa71
commit c481a87ab5
19 changed files with 214 additions and 79 deletions

View file

@ -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();

View file

@ -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++;
}

View file

@ -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();

View file

@ -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) {

View file

@ -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);

View file

@ -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

View file

@ -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,

View file

@ -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());
});

View file

@ -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());

View file

@ -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());

View file

@ -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();

View file

@ -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);

View file

@ -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);

View file

@ -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();
}
/**

View file

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

View file

@ -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) {

View file

@ -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;

View file

@ -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(

View file

@ -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);