From bbe4b89a0dac8bdfb64f68c42fa997f843c5010d Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Wed, 30 Oct 2019 11:46:30 -0400 Subject: [PATCH] GT-3155 - Copy Special - allow the Copy Special action to work on the current address when there is no selection --- .../CodeBrowserClipboardProvider.java | 51 ++++++----- .../ByteViewerClipboardProvider.java | 8 -- .../core/clipboard/ClipboardPluginTest.java | 86 ++++++++++++------- 3 files changed, 87 insertions(+), 58 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java index 04df545c3a..76183735f8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java @@ -48,7 +48,6 @@ import ghidra.program.model.symbol.*; import ghidra.program.util.*; import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; -import ghidra.util.task.TaskMonitorAdapter; public class CodeBrowserClipboardProvider extends ByteCopier implements ClipboardContentProviderService { @@ -211,16 +210,14 @@ public class CodeBrowserClipboardProvider extends ByteCopier @Override public List getCurrentCopyTypes() { - if (copyFromSelectionEnabled) { - return COPY_TYPES; - } - return EMPTY_LIST; + return COPY_TYPES; } @Override public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor) { + if (copyType == ADDRESS_TEXT_TYPE) { - return copyAddress(currentSelection.getAddresses(true)); + return copyAddress(); } else if (copyType == CODE_TEXT_TYPE) { return copyCode(monitor); @@ -235,17 +232,25 @@ public class CodeBrowserClipboardProvider extends ByteCopier return copyLabelsComments(false, true); } else if (copyType == BYTE_STRING_TYPE) { - String byteString = copyBytesAsString(currentSelection, true, monitor); + String byteString = copyBytesAsString(getSelectedAddresses(), true, monitor); return new ByteViewerTransferable(byteString); } else if (copyType == BYTE_STRING_NO_SPACE_TYPE) { - String byteString = copyBytesAsString(currentSelection, false, monitor); + String byteString = copyBytesAsString(getSelectedAddresses(), false, monitor); return new ByteViewerTransferable(byteString); } return null; } + private AddressSetView getSelectedAddresses() { + AddressSetView addressSet = currentSelection; + if (addressSet == null || addressSet.isEmpty()) { + return new AddressSet(currentLocation.getAddress()); + } + return currentSelection; + } + public void setSelection(ProgramSelection selection) { currentSelection = selection; copyFromSelectionEnabled = selection != null && !selection.isEmpty(); @@ -354,27 +359,32 @@ public class CodeBrowserClipboardProvider extends ByteCopier return new NonLabelStringTransferable(location.getOperandRepresentation()); } - private Transferable copyAddress(AddressIterator addressIterator) { + private Transferable copyAddress() { + + AddressSetView addressSet = getSelectedAddresses(); StringBuilder buffy = new StringBuilder(); - while (addressIterator.hasNext()) { - buffy.append(addressIterator.next()).append('\n'); + AddressIterator it = addressSet.getAddresses(true); + while (it.hasNext()) { + buffy.append(it.next()).append('\n'); } return createStringTransferable(buffy.toString()); } protected Transferable copyCode(TaskMonitor monitor) { + + AddressSetView addressSet = getSelectedAddresses(); try { TextLayoutGraphics g = new TextLayoutGraphics(); Rectangle rect = new Rectangle(2048, 2048); - AddressRangeIterator rangeItr = currentSelection.getAddressRanges(); + AddressRangeIterator rangeItr = addressSet.getAddressRanges(); while (rangeItr.hasNext()) { AddressRange curRange = rangeItr.next(); Address curAddress = curRange.getMinAddress(); Address maxAddress = curRange.getMaxAddress(); - // check curAddress against null because getAddressAfter(curAddress) returns null - // in certain situations + + // getAddressAfter(curAddress) returns null in certain situations while (curAddress != null && curAddress.compareTo(maxAddress) <= 0) { if (monitor.isCancelled()) { break; @@ -411,18 +421,19 @@ public class CodeBrowserClipboardProvider extends ByteCopier private Transferable copyByteString(Address address) { AddressSet set = new AddressSet(address); - return copyBytes(set, false, TaskMonitorAdapter.DUMMY_MONITOR); + return copyBytes(set, false, TaskMonitor.DUMMY); } private CodeUnitInfoTransferable copyLabelsComments(boolean copyLabels, boolean copyComments) { + + AddressSetView addressSet = getSelectedAddresses(); List list = new ArrayList<>(); - Address startAddr = currentSelection.getMinAddress(); - getCodeUnitInfo(currentSelection, startAddr, list, copyLabels, copyComments); + Address startAddr = addressSet.getMinAddress(); + getCodeUnitInfo(addressSet, startAddr, list, copyLabels, copyComments); return new CodeUnitInfoTransferable(list); } - @SuppressWarnings("unchecked") - // assumed correct data; handled in exception case + @SuppressWarnings("unchecked") // assumed correct data; handled in exception case private boolean pasteLabelsComments(Transferable pasteData, boolean pasteLabels, boolean pasteComments) { try { @@ -627,7 +638,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier @Override public boolean canCopySpecial() { - return copyFromSelectionEnabled; + return currentLocation != null; } private boolean canCopyCurrentLocationWithNoSelection() { diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerClipboardProvider.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerClipboardProvider.java index 3edbd252b8..d14d14e97e 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerClipboardProvider.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerClipboardProvider.java @@ -46,14 +46,6 @@ public class ByteViewerClipboardProvider extends ByteCopier return copyTypesList; } - private static final List PASTE_TYPES = createPasteTypesList(); - - private static List createPasteTypesList() { - List pasteTypesList = new LinkedList<>(); - pasteTypesList.add(BYTE_STRING_TYPE); - return pasteTypesList; - } - private boolean copyEnabled; private boolean pasteEnabled; private Set listeners = new CopyOnWriteArraySet<>(); diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java index 7fb6ca334f..94d4a3b0d8 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java @@ -42,7 +42,8 @@ import ghidra.app.plugin.core.byteviewer.*; import ghidra.app.plugin.core.clear.ClearCmd; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; -import ghidra.app.plugin.core.decompile.*; +import ghidra.app.plugin.core.decompile.DecompilerClipboardProvider; +import ghidra.app.plugin.core.decompile.DecompilerProvider; import ghidra.app.plugin.core.format.ByteBlockSelection; import ghidra.app.plugin.core.format.DataFormatModel; import ghidra.app.plugin.core.functiongraph.FGClipboardProvider; @@ -85,7 +86,6 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { private CodeBrowserClipboardProvider codeBrowserClipboardProvider; private CodeBrowserPlugin codeBrowserPlugin; private ByteViewerPlugin byteViewerPlugin; - private DecompilePlugin decompilePlugin; private CodeViewerProvider codeViewerProvider; private ClipboardPlugin clipboardPlugin; private ComponentProvider decompileProvider; @@ -198,7 +198,6 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { codeViewerWrapper = new CodeViewerWrapper(codeViewerProvider); tool.showComponentProvider(codeViewerProvider, true); - decompilePlugin = getPlugin(tool, DecompilePlugin.class); ComponentProvider decompiler = tool.getComponentProvider("Decompiler"); tool.showComponentProvider(decompiler, true); @@ -652,6 +651,43 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(commentText, comments[0]); } + @Test + public void testCodeBrowser_CopySpecial_WithSelection() throws Exception { + + DockingAction copySpecialAction = + getAction(codeBrowserClipboardProvider, COPY_SPECIAL_ACTION_NAME); + waitForSwing(); + assertFalse(copySpecialAction.isEnabled()); + + codeBrowserPlugin.goTo(new MnemonicFieldLocation(program, addr("1001050"))); + assertTrue(copySpecialAction.isEnabled()); + + makeSelection(codeViewerWrapper); + assertTrue(copySpecialAction.isEnabled()); + + copySpecial(codeViewerWrapper, copySpecialAction); + String clipboardContents = getClipboardContents(); + String expectedBytes = "f4 77 33 58 f4 77 91 45"; + assertEquals(expectedBytes, clipboardContents); + } + + @Test + public void testCodeBrowser_CopySpecial_WithoutSelection() throws Exception { + + DockingAction copySpecialAction = + getAction(codeBrowserClipboardProvider, COPY_SPECIAL_ACTION_NAME); + waitForSwing(); + assertFalse(copySpecialAction.isEnabled()); + + codeBrowserPlugin.goTo(new MnemonicFieldLocation(program, addr("1001050"))); + assertTrue(copySpecialAction.isEnabled()); + + copySpecial(codeViewerWrapper, copySpecialAction); + String clipboardContents = getClipboardContents(); + String expectedBytes = "e0"; + assertEquals(expectedBytes, clipboardContents); + } + @Test public void testCopyActionEnablement() { @@ -664,14 +700,14 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { waitForSwing(); - assertTrue(!byteViewerCopyAction.isEnabled()); - assertTrue(!codeBrowserCopyAction.isEnabled()); + assertFalse(byteViewerCopyAction.isEnabled()); + assertFalse(codeBrowserCopyAction.isEnabled()); // this action is enabled on any text - // assertTrue(!decompileCopyAction.isEnabled()); + // assertFalse(decompileCopyAction.isEnabled()); // give the providers focus and check their actions state - assertTrue(!byteViewerCopyAction.isEnabled()); + assertFalse(byteViewerCopyAction.isEnabled()); // the code browser is special--make sure that it not only has focus, but that the location // of the cursor is not one of the 'special' copy locations @@ -720,15 +756,15 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { DockingAction codeBrowserPasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); - assertTrue(!byteViewerPasteAction.isEnabled()); - assertTrue(!codeBrowserPasteAction.isEnabled()); + assertFalse(byteViewerPasteAction.isEnabled()); + assertFalse(codeBrowserPasteAction.isEnabled()); // For each provider make sure no paste on a selection without a copy: // check copy on selection state makeSelection(byteViewerWrapper); // check the state - assertTrue(!byteViewerPasteAction.isEnabled()); + assertFalse(byteViewerPasteAction.isEnabled()); // clear the selection byteViewerWrapper.clearSelection(); @@ -737,7 +773,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { codeViewerWrapper.clearSelection(); // check the state - assertTrue(!codeBrowserPasteAction.isEnabled()); + assertFalse(codeBrowserPasteAction.isEnabled()); // clear the selection codeViewerWrapper.clearSelection(); @@ -754,7 +790,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { copy(byteViewerWrapper, byteViewerCopyAction); // check the state - assertTrue(!byteViewerPasteAction.isEnabled()); + assertFalse(byteViewerPasteAction.isEnabled()); setByteViewerEditable(true); assertTrue(byteViewerPasteAction.isEnabled()); @@ -809,7 +845,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { makeSelection(byteViewerWrapper); copy(byteViewerWrapper, byteViewerCopyAction); - assertTrue(!byteViewerPasteAction.isEnabled()); + assertFalse(byteViewerPasteAction.isEnabled()); setByteViewerEditable(true); assertTrue(byteViewerPasteAction.isEnabled()); assertTrue(codeBrowserPasteAction.isEnabled()); @@ -818,8 +854,8 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { clearClipboardContents(); // sanity check - assertTrue(!byteViewerPasteAction.isEnabled()); - assertTrue(!codeBrowserPasteAction.isEnabled()); + assertFalse(byteViewerPasteAction.isEnabled()); + assertFalse(codeBrowserPasteAction.isEnabled()); // code browser to byte viewer and decompiler makeSelection(codeViewerWrapper); @@ -848,8 +884,8 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { clearClipboardContents(); // sanity check - assertTrue(!byteViewerPasteAction.isEnabled()); - assertTrue(!codeBrowserPasteAction.isEnabled()); + assertFalse(byteViewerPasteAction.isEnabled()); + assertFalse(codeBrowserPasteAction.isEnabled()); // copy from decompiler can not paste into the other two makeSelection(decompilerWrapper); @@ -1427,7 +1463,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { } private void copySpecial(ComponentProviderWrapper wrapper, final DockingAction copyAction) { - wrapper.verifySelection(); + executeOnSwingWithoutBlocking(() -> copyAction.actionPerformed(getActionContext(wrapper))); // get the dialog and make a selection @@ -1445,12 +1481,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { okButton.doClick(); }); - try { - waitForTasks(); - } - catch (Exception e) { - Assert.fail(); - } + waitForTasks(); } private void copySpecial_ByteStringNoSpaces(ComponentProviderWrapper wrapper, @@ -1473,12 +1504,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { okButton.doClick(); }); - try { - waitForTasks(); - } - catch (Exception e) { - Assert.fail("Unexpected exception waiting for tasks"); - } + waitForTasks(); } private void assertByteViewerBytes(String clipboardContents, Address address)