bytesState = executorStateForCoordinates(tool, coordinates);
+ return new WatchValuePcodeExecutorState(new WatchValuePcodeExecutorStatePiece(
+ bytesState,
+ new TraceMemoryStatePcodeExecutorStatePiece(data),
+ new AddressOfPcodeExecutorStatePiece(data.getLanguage()),
+ new AddressesReadTracePcodeExecutorStatePiece(data)));
+ }
+
+ /**
+ * Build an executor that can compute watch values
+ *
+ *
+ * This computes the concrete value, its state, its address, and the set of physical addresses
+ * involved in the computation. CAUTION: This executor's state will attempt to read live
+ * machine state, if applicable. Use the executor in a background thread to avoid locking the
+ * GUI.
+ *
+ * @param tool this plugin tool
+ * @param coordinates the coordinates providing context for the evaluation
+ * @return an executor for evaluating the watch
+ */
+ public static PcodeExecutor buildWatchExecutor(PluginTool tool,
+ DebuggerCoordinates coordinates) {
+ TracePlatform platform = coordinates.getPlatform();
+ Language language = platform.getLanguage();
+ if (!(language instanceof SleighLanguage slang)) {
+ throw new IllegalArgumentException("Watch expressions require a Sleigh language");
+ }
+ WatchValuePcodeExecutorState state = buildWatchState(tool, coordinates);
+ return new PcodeExecutor<>(slang, state.getArithmetic(), state, Reason.INSPECT);
+ }
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java
index a3b1acea2c..9ea8017be6 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java
@@ -486,20 +486,13 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
}
protected static LocationTrackingSpec getLocationTrackingSpec(String name) {
- return LocationTrackingSpec.fromConfigName(name);
+ return LocationTrackingSpecFactory.fromConfigName(name);
}
protected static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
return AutoReadMemorySpec.fromConfigName(name);
}
- protected final LocationTrackingSpec trackNone =
- getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
- protected final LocationTrackingSpec trackPc =
- getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
- protected final LocationTrackingSpec trackSp =
- getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
-
protected final AutoReadMemorySpec readNone =
getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
protected final AutoReadMemorySpec readVisible =
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 f148cf05eb..f1c2fd00f8 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
@@ -39,8 +39,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
-import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec;
-import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
+import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
@@ -271,7 +270,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceThread thread1;
TraceThread thread2;
DebuggerListingProvider extraProvider = SwingExecutorService.LATER
- .submit(() -> listingPlugin.createListingIfMissing(trackPc, true))
+ .submit(() -> listingPlugin.createListingIfMissing(PCLocationTrackingSpec.INSTANCE,
+ true))
.get();
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
@@ -330,7 +330,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
//Pre-check
assertEquals(tb.addr(0x00400000), listingProvider.getLocation().getAddress());
- listingProvider.setTrackingSpec(trackSp);
+ listingProvider.setTrackingSpec(SPLocationTrackingSpec.INSTANCE);
waitForSwing();
ProgramLocation loc = listingProvider.getLocation();
@@ -340,7 +340,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception {
- listingProvider.setTrackingSpec(trackNone);
+ listingProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
try ( //
ToyDBTraceBuilder b1 =
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
@@ -396,7 +396,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception {
- listingProvider.setTrackingSpec(trackNone);
+ listingProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
try ( //
ToyDBTraceBuilder b1 =
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
@@ -735,13 +735,17 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertArrayEquals(zero, buf.array());
runSwing(() -> trace.getProgramView().getMemory().setForceFullView(true));
- goToDyn(addr(trace, 0x55550000));
- waitRecorder(recorder);
- buf.clear();
- assertEquals(data.length,
- trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
- assertArrayEquals(data, buf.array());
+ waitForPass(noExc(() -> {
+ goToDyn(addr(trace, 0x55550000));
+ waitRecorder(recorder);
+
+ buf.clear();
+ assertEquals(data.length,
+ trace.getMemoryManager()
+ .getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
+ assertArrayEquals(data, buf.array());
+ }));
}
@Test
@@ -787,7 +791,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals("(nowhere)", listingProvider.locationLabel.getText());
DebuggerListingProvider extraProvider =
- runSwing(() -> listingPlugin.createListingIfMissing(trackNone, false));
+ runSwing(() -> listingPlugin.createListingIfMissing(NoneLocationTrackingSpec.INSTANCE,
+ false));
waitForSwing();
assertEquals(traceManager.getCurrentView(), extraProvider.getProgram());
assertEquals("(nowhere)", extraProvider.locationLabel.getText());
@@ -877,7 +882,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
waitForSwing();
// Check the default is track pc
- assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
goToDyn(tb.addr(0x00400000));
@@ -888,14 +894,16 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
performAction(listingProvider.actionTrackLocation);
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
- setActionStateWithTrigger(listingProvider.actionTrackLocation, trackSp,
+ setActionStateWithTrigger(listingProvider.actionTrackLocation,
+ SPLocationTrackingSpec.INSTANCE,
EventTrigger.GUI_ACTION);
waitForSwing();
assertEquals(tb.addr(0x1fff8765), listingProvider.getLocation().getAddress());
- listingProvider.setTrackingSpec(trackNone);
+ listingProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
waitForSwing();
- assertEquals(trackNone, listingProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(NoneLocationTrackingSpec.INSTANCE,
+ listingProvider.actionTrackLocation.getCurrentUserData());
}
@Test
@@ -1022,7 +1030,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
// NOTE: Action does not exist for main dynamic listing
DebuggerListingProvider extraProvider =
- runSwing(() -> listingPlugin.createListingIfMissing(trackNone, true));
+ runSwing(() -> listingPlugin.createListingIfMissing(NoneLocationTrackingSpec.INSTANCE,
+ true));
waitForSwing();
assertTrue(extraProvider.actionFollowsCurrentThread.isEnabled());
assertTrue(extraProvider.actionFollowsCurrentThread.isSelected());
@@ -1258,7 +1267,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testActivateTraceChangeLanguage() throws Exception {
- assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ listingProvider.actionTrackLocation.getCurrentUserData());
createSnaplessTrace("x86:LE:64:default");
@@ -1300,7 +1310,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testActivateThreadTracks() throws Exception {
- assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
Register pc = tb.language.getProgramCounter();
@@ -1331,7 +1342,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testActivateSnapTracks() throws Exception {
- assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
Register pc = tb.language.getProgramCounter();
@@ -1359,7 +1371,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testActivateFrameTracks() throws Exception {
- assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
TraceThread thread;
@@ -1387,7 +1400,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testRegsPCChangedTracks() throws Exception {
- assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
@@ -1417,7 +1431,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testRegsPCChangedTracksDespiteStackWithNoPC() throws Exception {
- assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
@@ -1451,7 +1466,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testStackPCChangedTracks() throws Exception {
- assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceStackManager sm = tb.trace.getStackManager();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java
index 282db53510..03125b39d6 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java
@@ -48,7 +48,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
-import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
+import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.services.DebuggerStateEditingService;
@@ -248,7 +248,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
TraceThread thread1;
TraceThread thread2;
DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.LATER
- .submit(() -> memBytesPlugin.createViewerIfMissing(trackPc, true))
+ .submit(() -> memBytesPlugin.createViewerIfMissing(PCLocationTrackingSpec.INSTANCE,
+ true))
.get();
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
@@ -307,7 +308,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
//Pre-check
assertNull(memBytesProvider.getLocation());
- runSwing(() -> memBytesProvider.setTrackingSpec(trackSp));
+ runSwing(() -> memBytesProvider.setTrackingSpec(SPLocationTrackingSpec.INSTANCE));
ProgramLocation loc = memBytesProvider.getLocation();
assertEquals(tb.trace.getProgramView(), loc.getProgram());
@@ -316,7 +317,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception {
- runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
+ runSwing(() -> memBytesProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE));
try ( //
ToyDBTraceBuilder b1 =
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
@@ -367,7 +368,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception {
- runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
+ runSwing(() -> memBytesProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE));
try ( //
ToyDBTraceBuilder b1 =
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
@@ -578,7 +579,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
assertEquals("(nowhere)", memBytesProvider.locationLabel.getText());
DebuggerMemoryBytesProvider extraProvider =
- runSwing(() -> memBytesPlugin.createViewerIfMissing(trackNone, false));
+ runSwing(() -> memBytesPlugin.createViewerIfMissing(NoneLocationTrackingSpec.INSTANCE,
+ false));
waitForSwing();
assertEquals(traceManager.getCurrentView(), extraProvider.getProgram());
assertEquals("(nowhere)", extraProvider.locationLabel.getText());
@@ -670,7 +672,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
waitForSwing();
// Check the default is track pc
- assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ memBytesProvider.actionTrackLocation.getCurrentUserData());
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
goToDyn(tb.addr(0x00400000));
@@ -681,13 +684,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
performAction(memBytesProvider.actionTrackLocation);
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
- setActionStateWithTrigger(memBytesProvider.actionTrackLocation, trackSp,
+ setActionStateWithTrigger(memBytesProvider.actionTrackLocation,
+ SPLocationTrackingSpec.INSTANCE,
EventTrigger.GUI_ACTION);
waitForSwing();
assertEquals(tb.addr(0x1fff8765), memBytesProvider.getLocation().getAddress());
- runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
- assertEquals(trackNone, memBytesProvider.actionTrackLocation.getCurrentUserData());
+ runSwing(() -> memBytesProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE));
+ assertEquals(NoneLocationTrackingSpec.INSTANCE,
+ memBytesProvider.actionTrackLocation.getCurrentUserData());
}
@Test
@@ -721,7 +726,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
// NOTE: Action does not exist for main dynamic listing
DebuggerMemoryBytesProvider extraProvider =
- runSwing(() -> memBytesPlugin.createViewerIfMissing(trackNone, true));
+ runSwing(() -> memBytesPlugin.createViewerIfMissing(NoneLocationTrackingSpec.INSTANCE,
+ true));
waitForSwing();
assertTrue(extraProvider.actionFollowsCurrentThread.isEnabled());
assertTrue(extraProvider.actionFollowsCurrentThread.isSelected());
@@ -902,7 +908,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testActivateThreadTracks() throws Exception {
- assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
Register pc = tb.language.getProgramCounter();
@@ -933,7 +940,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testActivateSnapTracks() throws Exception {
- assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
Register pc = tb.language.getProgramCounter();
@@ -961,7 +969,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testActivateFrameTracks() throws Exception {
- assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
TraceThread thread;
@@ -989,7 +998,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testRegsPCChangedTracks() throws Exception {
- assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
@@ -1019,7 +1029,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testRegsPCChangedTracksDespiteStackWithNoPC() throws Exception {
- assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
@@ -1053,7 +1064,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testStackPCChangedTracks() throws Exception {
- assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
+ assertEquals(PCLocationTrackingSpec.INSTANCE,
+ memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceStackManager sm = tb.trace.getStackManager();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java
index 269a48e68d..b036ff6545 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java
@@ -30,7 +30,6 @@ import com.google.common.collect.Range;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
-import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterDataSettingsDialog;
@@ -766,8 +765,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
addRegisterValues(thread);
addRegisterTypes(thread);
// Ensure cause is goto PC, not register tracking
- listingPlugin.setTrackingSpec(
- LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME));
+ listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
activateThread(thread);
waitForSwing();
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 cd7920221a..4257c6928b 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
@@ -22,7 +22,6 @@ import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;
-import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.*;
import com.google.common.collect.Range;
@@ -135,7 +134,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
- waitForSwing();
+ waitForWatches();
assertEquals("0x400000", row.getRawValueString());
assertEquals("", row.getValueString()); // NB. No data type set
@@ -153,8 +152,9 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
row.setExpression("r0");
setRegisterValues(thread);
+ waitForWatches();
- waitForPass(() -> assertEquals("0x400000", row.getRawValueString()));
+ assertEquals("0x400000", row.getRawValueString());
assertNoErr(row);
}
@@ -169,7 +169,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
- waitForSwing();
+ waitForWatches();
assertEquals("0x400000", row.getRawValueString());
assertEquals("400000h", row.getValueString());
@@ -190,7 +190,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
watchesProvider.watchFilterPanel.setSelectedItem(row);
- waitForSwing();
+ waitForWatches();
performEnabledAction(watchesProvider, watchesProvider.actionApplyDataType, true);
@@ -199,6 +199,12 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals(FormatSettingsDefinition.DECIMAL, format.getChoice(u400000));
}
+ protected void waitForWatches() {
+ waitForSwing();
+ watchesProvider.waitEvaluate(1000);
+ waitForSwing();
+ }
+
@Test
public void testWatchWithDataTypeSettings() {
setRegisterValues(thread);
@@ -210,7 +216,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
- waitForSwing();
+ waitForWatches();
assertEquals("0x400000", row.getRawValueString());
assertEquals("400000h", row.getValueString());
@@ -236,7 +242,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
- waitForSwing();
+ waitForWatches();
watchesProvider.watchFilterPanel.setSelectedItem(row);
waitForSwing();
@@ -263,7 +269,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
- waitForSwing();
+ waitForWatches();
assertEquals("0xdeadbeef", row.getRawValueString());
assertEquals("DEADBEEFh", row.getValueString());
@@ -285,7 +291,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
- waitForSwing();
+ waitForWatches();
assertEquals("0x400008", row.getRawValueString());
assertEquals("400008h", row.getValueString());
@@ -315,7 +321,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(trace);
traceManager.activateThread(thread);
- waitForSwing();
+ waitForWatches();
// Verify no target read has occurred yet
TraceMemorySpace regs =
@@ -331,15 +337,11 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
row.setExpression("*:4 r0");
row.setDataType(LongDataType.dataType);
+ waitForWatches();
- waitForPass(() -> {
- if (row.getError() != null) {
- ExceptionUtils.rethrow(row.getError());
- }
- assertEquals("{ 01 02 03 04 }", row.getRawValueString());
- assertEquals("1020304h", row.getValueString());
- });
assertNoErr(row);
+ assertEquals("{ 01 02 03 04 }", row.getRawValueString());
+ assertEquals("1020304h", row.getValueString());
}
protected void runTestIsEditableEmu(String expression, boolean expectWritable) {
@@ -353,7 +355,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
- waitForSwing();
+ waitForWatches();
assertNoErr(row);
assertFalse(row.isRawValueEditable());
@@ -387,6 +389,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ waitForWatches();
performAction(watchesProvider.actionEnableEdits);
@@ -554,6 +557,8 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
performAction(watchesProvider.actionAdd);
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
row.setExpression(expression);
+ waitForWatches();
+
performAction(watchesProvider.actionEnableEdits);
return row;
@@ -731,7 +736,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
rowR0.setDataType(PointerDataType.dataType);
registersProvider.setSelectedRow(rowR0);
});
- waitForSwing();
+ waitForWatches();
performEnabledAction(registersProvider, watchesProvider.actionAddFromRegister, true);
@@ -760,7 +765,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
row.setExpression("*:8 r0");
traceManager.activateThread(thread);
- waitForSwing();
+ waitForWatches();
assertEquals(symbol, row.getSymbol());
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java
index e7bd20feba..6647c15bf8 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java
@@ -34,8 +34,13 @@ import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorState;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Register;
+import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.model.Trace;
+import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
+import ghidra.trace.model.time.TraceSnapshot;
+import ghidra.trace.model.time.schedule.TraceSchedule;
+import ghidra.util.database.UndoableTransaction;
/**
* Test the {@link DirectBytesTracePcodeExecutorState} in combination with
@@ -86,6 +91,59 @@ public class TraceRecorderPcodeExecTest extends AbstractGhidraHeadedDebuggerGUIT
assertEquals(11, Utils.bytesToLong(result, result.length, language.isBigEndian()));
}
+ @Test
+ public void testExecutorEvalInScratchReadsLive() throws Throwable {
+ createTestModel();
+ mb.createTestProcessesAndThreads();
+
+ mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
+ Register::isBaseRegister);
+ TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
+ waitOn(regs.writeRegistersNamed(Map.of(
+ "r0", new byte[] { 5 },
+ "r1", new byte[] { 6 })));
+
+ TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
+ createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
+
+ TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
+ Trace trace = recorder.getTrace();
+ SleighLanguage language = (SleighLanguage) trace.getBaseLanguage();
+
+ PcodeExpression expr = SleighProgramCompiler
+ .compileExpression(language, "r0 + r1");
+
+ Register r0 = language.getRegister("r0");
+ Register r1 = language.getRegister("r1");
+ waitForPass(() -> {
+ // TODO: A little brittle: Depends on a specific snap advancement strategy
+ assertEquals(3, trace.getTimeManager().getSnapshotCount());
+ DebuggerRegisterMapper rm = recorder.getRegisterMapper(thread);
+ assertNotNull(rm);
+ assertNotNull(rm.getTargetRegister("r0"));
+ assertNotNull(rm.getTargetRegister("r1"));
+ assertTrue(rm.getRegistersOnTarget().contains(r0));
+ assertTrue(rm.getRegistersOnTarget().contains(r1));
+ });
+
+ TraceSchedule oneTick = TraceSchedule.snap(recorder.getSnap()).steppedForward(thread, 1);
+ try (UndoableTransaction tid = UndoableTransaction.start(trace, "Scratch")) {
+ TraceSnapshot scratch = trace.getTimeManager().getSnapshot(Long.MIN_VALUE, true);
+ scratch.setSchedule(oneTick);
+ scratch.setDescription("Faked");
+
+ TraceMemorySpace space = trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
+ space.setValue(scratch.getKey(), new RegisterValue(r0, BigInteger.valueOf(10)));
+ }
+
+ PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool,
+ DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread).time(oneTick));
+
+ // In practice, this should be backgrounded, but we're in a test thread
+ byte[] result = expr.evaluate(executor);
+ assertEquals(16, Utils.bytesToLong(result, result.length, language.isBigEndian()));
+ }
+
@Test
public void testExecutorWrite() throws Throwable {
createTestModel();
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java
new file mode 100644
index 0000000000..415e580eb1
--- /dev/null
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java
@@ -0,0 +1,114 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.pcode.exec.trace;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.help.UnsupportedOperationException;
+
+import ghidra.pcode.exec.*;
+import ghidra.pcode.exec.PcodeArithmetic.Purpose;
+import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
+import ghidra.program.model.address.*;
+import ghidra.program.model.mem.MemBuffer;
+
+/**
+ * An auxilliary state piece that reports the (trace) address ranges
+ *
+ *
+ * Except for unique spaces, sets are ignored, and gets simply echo back the range of addresses of
+ * the requested read. In unique spaces, the "addresses read" is treated as the value, so that
+ * values transiting unique space can correct have their source address ranges reported. Use this
+ * with {@link AddressesReadPcodeArithmetic} to compute the union of these ranges during Sleigh
+ * expression evaluation. The ranges are translated from the guest platform, if applicable, to the
+ * trace address. In the case of registers, the addresses are also translated to the appropriate
+ * overlay space, if applicable.
+ */
+public class AddressesReadTracePcodeExecutorStatePiece
+ extends AbstractLongOffsetPcodeExecutorStatePiece
+ implements TracePcodeExecutorStatePiece {
+
+ protected final PcodeTraceDataAccess data;
+ private final Map unique = new HashMap<>();
+
+ /**
+ * Construct the state piece
+ *
+ * @param data the trace data access shim
+ */
+ public AddressesReadTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
+ super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()),
+ AddressesReadPcodeArithmetic.INSTANCE);
+ this.data = data;
+ }
+
+ @Override
+ public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
+ throw new ConcretionError("Cannot make 'addresses read' concrete buffers", purpose);
+ }
+
+ @Override
+ public PcodeTraceDataAccess getData() {
+ return data;
+ }
+
+ @Override
+ public void writeDown(PcodeTraceDataAccess into) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) {
+ return space;
+ }
+
+ @Override
+ protected void setInSpace(AddressSpace space, long offset, int size, AddressSetView val) {
+ if (!space.isUniqueSpace()) {
+ return;
+ }
+ // TODO: size is not considered
+ unique.put(offset, val);
+ }
+
+ @Override
+ protected AddressSetView getFromSpace(AddressSpace space, long offset, int size,
+ Reason reason) {
+ if (space.isUniqueSpace()) {
+ AddressSetView result = unique.get(offset);
+ if (result == null) {
+ return new AddressSet();
+ }
+ return result;
+ }
+ Address start = data.translate(space.getAddress(offset));
+ if (start == null) {
+ return new AddressSet();
+ }
+ try {
+ return new AddressSet(new AddressRangeImpl(start, size));
+ }
+ catch (AddressOverflowException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public void clear() {
+ unique.clear();
+ }
+}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java
index 39059c08c0..195e634797 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java
@@ -67,7 +67,7 @@ public class DirectBytesTracePcodeExecutorStatePiece
*
* @param data the trace-data access shim
*/
- protected DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
+ public DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data);
}
@@ -138,4 +138,9 @@ public class DirectBytesTracePcodeExecutorStatePiece
public void writeDown(PcodeTraceDataAccess into) {
// Writes directly, so just ignore
}
+
+ @Override
+ public void clear() {
+ unique.clear();
+ }
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java
index 02ae1be60f..e9ca1f7c45 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java
@@ -120,4 +120,9 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new ConcretionError("Cannot make TraceMemoryState into a concrete buffer", purpose);
}
+
+ @Override
+ public void clear() {
+ unique.clear();
+ }
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java
index dfb24dc234..8176d97cd6 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java
@@ -51,6 +51,11 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
this.mm = platform.getTrace().getMemoryManager();
}
+ @Override
+ public TraceTimeViewport getViewport() {
+ return viewport;
+ }
+
@Override
public Language getLanguage() {
return platform.getLanguage();
@@ -186,6 +191,15 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
return ops.getViewBytes(snap, toOverlay(hostStart), buf);
}
+ @Override
+ public Address translate(Address address) {
+ Address host = platform.mapGuestToHost(address);
+ if (host == null) {
+ return null;
+ }
+ return toOverlay(host);
+ }
+
@Override
public PcodeTracePropertyAccess getPropertyAccess(String name, Class type) {
return new DefaultPcodeTracePropertyAccess<>(this, name, type);
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java
index 15133d25c1..fc06a9659f 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java
@@ -46,12 +46,12 @@ public class DefaultPcodeTraceAccess extends AbstractPcodeTraceAccess //
}
@Override
- public DefaultPcodeTraceMemoryAccess newDataForSharedState() {
+ protected DefaultPcodeTraceMemoryAccess newDataForSharedState() {
return new DefaultPcodeTraceMemoryAccess(platform, snap, viewport);
}
@Override
- public DefaultPcodeTraceRegistersAccess newDataForLocalState(TraceThread thread, int frame) {
+ protected DefaultPcodeTraceRegistersAccess newDataForLocalState(TraceThread thread, int frame) {
return new DefaultPcodeTraceRegistersAccess(platform, snap, thread, frame, viewport);
}
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java
index 31ab70302b..ab04af2030 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java
@@ -101,6 +101,14 @@ public class DefaultPcodeTraceThreadAccess
return memory.getBytes(start, buf);
}
+ @Override
+ public Address translate(Address address) {
+ if (address.isRegisterAddress()) {
+ return registers.translate(address);
+ }
+ return memory.translate(address);
+ }
+
@Override
public PcodeTracePropertyAccess getPropertyAccess(String name, Class type) {
throw new UnsupportedOperationException("This is meant for p-code executor use");
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/InternalPcodeTraceDataAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/InternalPcodeTraceDataAccess.java
index ad349ff26a..708f365df6 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/InternalPcodeTraceDataAccess.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/InternalPcodeTraceDataAccess.java
@@ -16,6 +16,7 @@
package ghidra.pcode.exec.trace.data;
import ghidra.lifecycle.Internal;
+import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.property.TracePropertyMapOperations;
@@ -27,4 +28,6 @@ public interface InternalPcodeTraceDataAccess extends PcodeTraceDataAccess {
TracePropertyMapOperations getPropertyOps(String name, Class type,
boolean createIfAbsent);
+
+ TraceTimeViewport getViewport();
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java
index 3bd560bec7..d0c59b5917 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java
@@ -106,6 +106,18 @@ public interface PcodeTraceAccess {
*/
PcodeTraceRegistersAccess getDataForLocalState(TraceThread thread, int frame);
+ /**
+ * Construct a new trace thread data-access shim
+ *
+ * @param shared the shared (memory) state
+ * @param local the local (register) state
+ * @return the thread data-access shim
+ */
+ default PcodeTraceDataAccess newPcodeTraceThreadAccess(PcodeTraceMemoryAccess shared,
+ PcodeTraceRegistersAccess local) {
+ return new DefaultPcodeTraceThreadAccess(shared, local);
+ }
+
/**
* Get the data-access shim for use in an executor having thread context
*
@@ -123,7 +135,7 @@ public interface PcodeTraceAccess {
if (thread == null) {
return getDataForSharedState();
}
- return new DefaultPcodeTraceThreadAccess(getDataForSharedState(),
+ return newPcodeTraceThreadAccess(getDataForSharedState(),
getDataForLocalState(thread, frame));
}
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java
index 41171e4030..0c9820544e 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java
@@ -123,6 +123,14 @@ public interface PcodeTraceDataAccess {
*/
int getBytes(Address start, ByteBuffer buf);
+ /**
+ * Translate the given emulator address to a host/overlay address
+ *
+ * @param address the emulator address
+ * @return the host/overlay address
+ */
+ Address translate(Address address);
+
/**
* Get a property-access shim for the named property
*
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java
index 9a06a946a7..5fd70fbce1 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java
@@ -291,6 +291,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
}
}
+ @Override
public List> getOrderedSpans() {
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceTimeViewport.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceTimeViewport.java
index 1f5cb1b385..0d059e0ff6 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceTimeViewport.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceTimeViewport.java
@@ -173,6 +173,9 @@ public interface TraceTimeViewport {
AddressSet computeVisibleParts(AddressSetView set, Range lifespan, T object,
Occlusion occlusion);
+
+ List> getOrderedSpans();
+
/**
* Get the snaps involved in the view in most-recent-first order
*
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java
index 8d7429c291..fec087c579 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java
@@ -112,4 +112,17 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState {
public PcodeExecutorState getLocalState() {
return localState;
}
+
+ /**
+ * {@inheritDoc}
+ *
+ *
+ * This will only clear the thread's local state, lest we invoke clear on the shared state for
+ * every thread. Instead, if necessary, the machine should clear its local state then clear each
+ * thread's local state.
+ */
+ @Override
+ public void clear() {
+ localState.clear();
+ }
}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java
index 54c3d384aa..f59267a974 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java
@@ -142,4 +142,11 @@ public abstract class AbstractBytesPcodeExecutorStatePiece {
return null;
}
+ @Override
+ public Address fromConst(BigInteger value, int size) {
+ return null;
+ }
+
@Override
public Address fromConst(long value, int size) {
return null;
@@ -80,7 +85,7 @@ public enum AddressOfPcodeArithmetic implements PcodeArithmetic {
@Override
public byte[] toConcrete(Address value, Purpose purpose) {
- throw new ConcretionError("Cannot decide branches using 'address of'", purpose);
+ throw new ConcretionError("Cannot make 'address of' concrete", purpose);
}
@Override
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java
index 77e7aaa350..fccad463bd 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java
@@ -70,6 +70,7 @@ public class AddressOfPcodeExecutorStatePiece
if (!space.isUniqueSpace()) {
return;
}
+ // TODO: size is not considered
long lOffset = addressArithmetic.toLong(offset, Purpose.STORE);
unique.put(lOffset, val);
}
@@ -88,4 +89,9 @@ public class AddressOfPcodeExecutorStatePiece
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new ConcretionError("Cannot make 'address of' concrete buffers", purpose);
}
+
+ @Override
+ public void clear() {
+ unique.clear();
+ }
}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java
new file mode 100644
index 0000000000..4d3ac04a73
--- /dev/null
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java
@@ -0,0 +1,91 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.pcode.exec;
+
+import java.math.BigInteger;
+
+import javax.help.UnsupportedOperationException;
+
+import ghidra.program.model.address.AddressSet;
+import ghidra.program.model.address.AddressSetView;
+import ghidra.program.model.lang.Endian;
+
+/**
+ * An auxilliary arithmetic that reports the union of all addresses read, typically during the
+ * evaluation of an expression.
+ */
+public enum AddressesReadPcodeArithmetic implements PcodeArithmetic {
+ /** The singleton instance */
+ INSTANCE;
+
+ @Override
+ public Endian getEndian() {
+ return null;
+ }
+
+ @Override
+ public AddressSetView unaryOp(int opcode, int sizeout, int sizein1, AddressSetView in1) {
+ return in1;
+ }
+
+ @Override
+ public AddressSetView binaryOp(int opcode, int sizeout, int sizein1, AddressSetView in1,
+ int sizein2, AddressSetView in2) {
+ return in1.union(in2);
+ }
+
+ @Override
+ public AddressSetView modBeforeStore(int sizeout, int sizeinAddress, AddressSetView inAddress,
+ int sizeinValue, AddressSetView inValue) {
+ return inValue;
+ }
+
+ @Override
+ public AddressSetView modAfterLoad(int sizeout, int sizeinAddress, AddressSetView inAddress,
+ int sizeinValue, AddressSetView inValue) {
+ return inValue.union(inAddress);
+ }
+
+ @Override
+ public AddressSetView fromConst(byte[] value) {
+ return new AddressSet();
+ }
+
+ @Override
+ public AddressSetView fromConst(BigInteger value, int size, boolean isContextreg) {
+ return new AddressSet();
+ }
+
+ @Override
+ public AddressSetView fromConst(BigInteger value, int size) {
+ return new AddressSet();
+ }
+
+ @Override
+ public AddressSetView fromConst(long value, int size) {
+ return new AddressSet();
+ }
+
+ @Override
+ public byte[] toConcrete(AddressSetView value, Purpose purpose) {
+ throw new ConcretionError("Cannot make 'addresses read' concrete", purpose);
+ }
+
+ @Override
+ public long sizeOf(AddressSetView value) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java
index 539869423a..cd0355f8dc 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java
@@ -172,4 +172,8 @@ public class BytesPcodeExecutorStateSpace {
}
return readBytes(offset, size, reason);
}
+
+ public void clear() {
+ bytes.clear();
+ }
}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java
index 161386a101..9aea5e3053 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java
@@ -70,4 +70,9 @@ public class DefaultPcodeExecutorState implements PcodeExecutorState {
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return piece.getConcreteBuffer(address, purpose);
}
+
+ @Override
+ public void clear() {
+ piece.clear();
+ }
}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java
index a824ddaaaf..ea69019d56 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java
@@ -116,4 +116,9 @@ public class PairedPcodeExecutorState implements PcodeExecutorState
* To compose three or more states, first ask if it is really necessary. Second, consider
- * implementing the {@link PcodeExecutorStatePiece} interface directly. Third, use the Church-style
- * triple. In that third case, the implementor must decide which side has the nested tuple. Putting
- * it on the right keeps the concrete piece (conventionally on the left) in the most shallow
- * position, so it can be accessed efficiently. However, putting it on the left (implying it's in
- * the deepest position) keeps the concrete piece near the other pieces to which it's most closely
- * bound. The latter goal is only important when the paired arithmetics mix information between
- * their elements.
+ * implementing the {@link PcodeExecutorStatePiece} interface for a record type. Third, use the
+ * Church-style triple. In that third case, it is recommended to compose the nested pair on the
+ * right of the top pair: Compose the two right pieces into a single piece, then use
+ * {@link PairedPcodeExecutorState} to compose a concrete state with the composed piece, yielding a
+ * state of triples. This can be applied ad nauseam to compose arbitrarily large tuples; however, at
+ * a certain point clients should consider creating a record and implementing the state piece and/or
+ * state interface. It's helpful to use this implementation as a reference. Alternatively, the
+ * {@code Debugger} module has a {@code WatchValuePcodeExecutorState} which follows this
+ * recommendation.
*
* @see PairedPcodeExecutorState
* @param the type of offset, usually the type of a controlling state
@@ -122,4 +124,10 @@ public class PairedPcodeExecutorStatePiece
public PcodeExecutorStatePiece getRight() {
return right;
}
+
+ @Override
+ public void clear() {
+ left.clear();
+ right.clear();
+ }
}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java
index a968f3afb3..80ff70d1f1 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java
@@ -238,4 +238,15 @@ public interface PcodeExecutorStatePiece {
default long quantizeOffset(AddressSpace space, long offset) {
return space.truncateAddressableWordOffset(offset) * space.getAddressableUnitSize();
}
+
+ /**
+ * Erase the entire state or piece
+ *
+ *
+ * This is generally only useful when the state is itself a cache to another object. This will
+ * ensure the state is reading from that object rather than a stale cache. If this is not a
+ * cache, this could in fact clear the whole state, and the machine using it will be left in the
+ * dark.
+ */
+ void clear();
}
diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java
index f770b3aa40..2fe8b9e30d 100644
--- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java
+++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java
@@ -115,4 +115,11 @@ public abstract class AbstractTaintPcodeExecutorStatePiece
protected TaintVec getFromSpace(S space, long offset, int size, Reason reason) {
return space.get(offset, size);
}
+
+ @Override
+ public void clear() {
+ for (S space : spaceMap.values()) {
+ space.clear();
+ }
+ }
}
diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java
index adaad69561..fe2dc0d7b5 100644
--- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java
+++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java
@@ -62,9 +62,9 @@ public class TaintSpace {
* Retrieve the taint sets for the variable at the given offset
*
*
- * This retrieves as many taint sets as there are elements in the given buffer vector. This first
- * element becomes the taint set at the given offset, then each subsequent element becomes the
- * taint set at each subsequent offset until the vector is filled. This is analogous to the
+ * This retrieves as many taint sets as there are elements in the given buffer vector. This
+ * first element becomes the taint set at the given offset, then each subsequent element becomes
+ * the taint set at each subsequent offset until the vector is filled. This is analogous to the
* manner in which bytes would be "read" from concrete state, starting at a given offset, into a
* destination array.
*
@@ -108,4 +108,8 @@ public class TaintSpace {
protected TaintSet whenNull(long offset) {
return TaintSet.EMPTY;
}
+
+ public void clear() {
+ taints.clear();
+ }
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiStateActionBuilder.java b/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiStateActionBuilder.java
index 86be342b54..43711cdda2 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiStateActionBuilder.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiStateActionBuilder.java
@@ -18,6 +18,7 @@ package docking.action.builder;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
+import java.util.function.Supplier;
import javax.swing.Icon;
@@ -26,7 +27,7 @@ import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
-/**
+/**
* Builder for {@link MultiStateDockingAction}
*
* @param The action state type
@@ -38,9 +39,11 @@ public class MultiStateActionBuilder extends
private boolean useCheckboxForIcons;
private List> states = new ArrayList<>();
+ private Supplier>> generator = null;
/**
* Builder constructor
+ *
* @param name the name of the action to be built
* @param owner the owner of the action to be build
*/
@@ -55,8 +58,10 @@ public class MultiStateActionBuilder extends
/**
* Sets the primary callback to be executed when this action changes its action state.
- * This builder will throw an {@link IllegalStateException} if one of the build methods is
- * called without providing this callback
+ *
+ *
+ * This builder will throw an {@link IllegalStateException} if one of the build methods is
+ * called without providing this callback.
*
* @param biConsumer the callback to execute when the selected action state is changed.
* @return this builder (for chaining)
@@ -69,10 +74,12 @@ public class MultiStateActionBuilder extends
}
/**
- * Overrides the default icons for actions shown in popup menu of the multi-state action. By
- * default, the popup menu items will use the icons as provided by the {@link ActionState}.
- * By passing true to this method, icons will not be used in the popup menu. Instead, a
- * checkbox icon will be used to show the active action state.
+ * Overrides the default icons for actions shown in popup menu of the multi-state action.
+ *
+ *
+ * By default, the popup menu items will use the icons as provided by the {@link ActionState}.
+ * By passing true to this method, icons will not be used in the popup menu. Instead, a checkbox
+ * icon will be used to show the active action state.
*
* @param b true to use a checkbox
* @return this MultiActionDockingActionBuilder (for chaining)
@@ -83,7 +90,7 @@ public class MultiStateActionBuilder extends
}
/**
- * Add an action state
+ * Add an action state
*
* @param displayName the name to appear in the action menu
* @param icon the icon to appear in the action menu
@@ -96,7 +103,7 @@ public class MultiStateActionBuilder extends
}
/**
- * Add an action state
+ * Add an action state
*
* @param actionState the action state to add
* @return this MultiActionDockingActionBuilder (for chaining)
@@ -107,7 +114,7 @@ public class MultiStateActionBuilder extends
}
/**
- * Add a list of action states
+ * Add a list of action states
*
* @param list a list of ActionStates;
* @return this MultiActionDockingActionBuilder (for chaining)
@@ -117,6 +124,21 @@ public class MultiStateActionBuilder extends
return self();
}
+ /**
+ * Generate the states dynamically upon the user clicking the button
+ *
+ *
+ * It is highly recommended that the current state is included in the list of available states.
+ * Otherwise, the user could become confused or frustrated.
+ *
+ * @param generator a function from action context to available states
+ * @return this MultiActionDockingActionBuilder (for chaining)
+ */
+ public MultiStateActionBuilder stateGenerator(Supplier>> generator) {
+ this.generator = generator;
+ return self();
+ }
+
@Override
public MultiStateDockingAction build() {
validate();
@@ -138,6 +160,16 @@ public class MultiStateActionBuilder extends
super.actionPerformed(context);
}
}
+
+ @Override
+ protected List> getStates() {
+ if (generator == null) {
+ return super.getStates();
+ }
+ else {
+ return generator.get();
+ }
+ }
};
for (ActionState actionState : states) {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/ActionState.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/ActionState.java
index 4e4f498d15..e104fb503a 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/menu/ActionState.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/ActionState.java
@@ -15,15 +15,17 @@
*/
package docking.menu;
+import java.util.Objects;
+
import javax.swing.Icon;
import ghidra.util.HelpLocation;
-import ghidra.util.SystemUtilities;
/**
- * Note: this class overrides the equals(Object)
and relies upon the equals
- * method of the userData
object. Thus, if it is important that equals work for you in
- * the non-standard identity way, then you must override equals
in your user data objects.
+ * Note: this class overrides the equals(Object)
and relies upon the
+ * equals
method of the userData
object. Thus, if it is important that
+ * equals work for you in the non-standard identity way, then you must override equals
+ * in your user data objects.
*
* @param the type of the action state
*/
@@ -73,7 +75,7 @@ public class ActionState {
ActionState> otherState = (ActionState>) other;
- if (!SystemUtilities.isEqual(userData, otherState.userData)) {
+ if (!Objects.equals(userData, otherState.userData)) {
return false;
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java
index 1fd1b5142c..7d35553615 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java
@@ -31,14 +31,18 @@ import help.Help;
import resources.icons.EmptyIcon;
/**
- * An action that can be in one of multiple states. The button of this action has a
- * drop-down icon that allows users to change the state of the button. As the user changes the
- * state of this action, {@link #actionStateChanged(ActionState, EventTrigger)} will be called.
- * Clients may also use the button of this action to respond to button presses by overriding
+ * An action that can be in one of multiple states.
+ *
+ *
+ * The button of this action has a drop-down icon that allows users to change the state of the
+ * button. As the user changes the state of this action,
+ * {@link #actionStateChanged(ActionState, EventTrigger)} will be called. Clients may also use the
+ * button of this action to respond to button presses by overriding
* {@link #actionPerformed(ActionContext)}.
*
- *
This action is intended primarily for use as toolbar actions. Alternatively, some clients
- * use this action to add a button to custom widgets. In the custom usage case, clients should use
+ *
+ * This action is intended primarily for use as toolbar actions. Alternatively, some clients use
+ * this action to add a button to custom widgets. In the custom use case, clients should use
* {@link NonToolbarMultiStateAction}.
*
* @param the type of the user data
@@ -49,7 +53,7 @@ public abstract class MultiStateDockingAction extends DockingAction {
private static Icon EMPTY_ICON = new EmptyIcon(16, 16);
private List> actionStates = new ArrayList<>();
- private int currentStateIndex = -1;
+ private ActionState currentState = null;
private MultiActionDockingActionIf multiActionGenerator;
private MultipleActionDockingToolbarButton multipleButton;
@@ -57,11 +61,10 @@ public abstract class MultiStateDockingAction extends DockingAction {
private boolean useCheckboxForIcons;
/**
- * Call this constructor with this action will not be added to a toolbar
+ * Constructor
*
* @param name the action name
* @param owner the owner
- * @see #MultiStateDockingAction(String, String, boolean)
*/
public MultiStateDockingAction(String name, String owner) {
super(name, owner);
@@ -71,22 +74,9 @@ public abstract class MultiStateDockingAction extends DockingAction {
super.setToolBarData(new ToolBarData(null));
}
- /**
- * Use this constructor explicitly when this action is used in a toolbar, passing true
- * for isToolbarAction
(see the javadoc header note).
- *
- * @param name the action name
- * @param owner the owner
- * @param isToolbarAction true if this action is a toolbar action
- * @deprecated use {@link #MultiStateDockingAction(String, String)}
- */
- @Deprecated(forRemoval = true, since = "10.2")
- protected MultiStateDockingAction(String name, String owner, boolean isToolbarAction) {
- this(name, owner);
- }
-
/**
* This method will be called as the user changes the selected button state
+ *
* @param newActionState the newly selected state
* @param trigger the source of the event
*/
@@ -94,11 +84,12 @@ public abstract class MultiStateDockingAction extends DockingAction {
/**
* This method is called when the user clicks the button when this action is used as part of
- * the default {@link DockingAction} framework.
+ * the default {@link DockingAction} framework.
*
- * This is the callback to be overridden when the child wishes to respond to user button
- * presses that are on the button and not the drop-down. The default behavior is to show the
- * popup menu when the button is clicked.
+ *
+ * This is the callback to be overridden when the child wishes to respond to user button presses
+ * that are on the button and not the drop-down. The default behavior is to show the popup menu
+ * when the button is clicked.
*/
@Override
public void actionPerformed(ActionContext context) {
@@ -106,10 +97,12 @@ public abstract class MultiStateDockingAction extends DockingAction {
}
/**
- * Overrides the default icons for actions shown in popup menu of the multi-state action. By
- * default, the popup menu items will use the icons as provided by the {@link ActionState}.
- * By passing true to this method, icons will not be used in the popup menu. Instead, a
- * checkbox icon will be used to show the active action state.
+ * Overrides the default icons for actions shown in popup menu of the multi-state action.
+ *
+ *
+ * By default, the popup menu items will use the icons as provided by the {@link ActionState}.
+ * By passing true to this method, icons will not be used in the popup menu. Instead, a checkbox
+ * icon will be used to show the active action state.
*
* @param useCheckboxForIcons true to use a checkbox
*/
@@ -118,9 +111,11 @@ public abstract class MultiStateDockingAction extends DockingAction {
}
/**
- * Sets the icon to use if the active action state does not supply an icon. This is useful if
- * you wish for your action states to not use icon, but desire the action itself to have an
- * icon.
+ * Sets the icon to use if the active action state does not supply an icon.
+ *
+ *
+ * This is useful if you wish for your action states to not use icon, but desire the action
+ * itself to have an icon.
*
* @param icon the icon
*/
@@ -128,18 +123,38 @@ public abstract class MultiStateDockingAction extends DockingAction {
this.defaultIcon = icon;
}
+ /**
+ * Extension point: Get the states to display when the button is clicked
+ *
+ *
+ * This is called when the button is clicked, immediately before the menu is displayed. It is
+ * generally recommended to ensure the current state is included in this list. The states will
+ * be displayed in the order of the returned list.
+ *
+ * @return the list of possible states
+ */
+ protected List> getStates() {
+ return actionStates;
+ }
+
+ private void updateStates() {
+ List> newStates = getStates();
+ if (newStates.equals(actionStates)) {
+ return;
+ }
+ actionStates.clear();
+ actionStates.addAll(newStates);
+ }
+
protected List getStateActions() {
- ActionState selectedState = actionStates.get(currentStateIndex);
+ updateStates();
List actions = new ArrayList<>(actionStates.size());
for (ActionState actionState : actionStates) {
-
- //@formatter:off
- boolean isSelected = actionState == selectedState;
- DockingActionIf a = useCheckboxForIcons ?
- new ActionStateToggleAction(actionState, isSelected) :
- new ActionStateAction(actionState, isSelected);
+ boolean isSelected = actionState.equals(currentState);
+ DockingActionIf a = useCheckboxForIcons
+ ? new ActionStateToggleAction(actionState, isSelected)
+ : new ActionStateAction(actionState, isSelected);
actions.add(a);
- //@formatter:on
}
return actions;
}
@@ -155,8 +170,8 @@ public abstract class MultiStateDockingAction extends DockingAction {
}
/**
- * add the supplied {@code ActionState}
- * if {@code fireFirstEvent} is {@code true} the first one will fire its event
+ * Add the supplied {@code ActionState}.
+ *
* @param actionState the {@code ActionState} to add
*/
public void addActionState(ActionState actionState) {
@@ -175,11 +190,11 @@ public abstract class MultiStateDockingAction extends DockingAction {
}
public T getCurrentUserData() {
- return actionStates.get(currentStateIndex).getUserData();
+ return currentState == null ? null : currentState.getUserData();
}
public ActionState getCurrentState() {
- return actionStates.get(currentStateIndex);
+ return currentState;
}
public List> getAllActionStates() {
@@ -187,6 +202,7 @@ public abstract class MultiStateDockingAction extends DockingAction {
}
public void setCurrentActionStateByUserData(T t) {
+ updateStates();
for (ActionState actionState : actionStates) {
// Note: most clients will pass a T that is already in our list. However, to be more
@@ -194,7 +210,7 @@ public abstract class MultiStateDockingAction extends DockingAction {
// problem using equals() here.
// if (actionState.getUserData() == t) {
if (actionState.getUserData().equals(t)) {
- setCurrentActionState(actionState);
+ doSetCurrentActionState(actionState, EventTrigger.API_CALL);
return;
}
}
@@ -208,13 +224,16 @@ public abstract class MultiStateDockingAction extends DockingAction {
}
public void setCurrentActionStateWithTrigger(ActionState actionState, EventTrigger trigger) {
- int indexOf = actionStates.indexOf(actionState);
- if (indexOf < 0) {
+ updateStates();
+ doSetCurrentActionState(actionState, trigger);
+ }
+
+ protected void doSetCurrentActionState(ActionState actionState, EventTrigger trigger) {
+ if (!actionStates.contains(actionState)) {
throw new IllegalArgumentException(
"Attempted to set actionState to unknown ActionState.");
}
- currentStateIndex = indexOf;
-
+ currentState = actionState;
setButtonState(actionState);
ToolBarData tbd = getToolBarData();
@@ -240,9 +259,8 @@ public abstract class MultiStateDockingAction extends DockingAction {
@Override
public JButton doCreateButton() {
multipleButton = new MultipleActionDockingToolbarButton(multiActionGenerator);
- if (currentStateIndex >= 0) {
- ActionState actionState = actionStates.get(currentStateIndex);
- setButtonState(actionState);
+ if (currentState != null) {
+ setButtonState(currentState);
}
return multipleButton;