diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/Clipboard.htm b/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/Clipboard.htm index 5594229414..ee5ba50b20 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/Clipboard.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/Clipboard.htm @@ -165,6 +165,15 @@
  • Byte String (No Spaces) - This is the same as the Byte String format, except there are no delimiting spaces between bytes.
  • +
  • Python Byte String - Copies the bytes into the Python byte string format, for + example: b'\x41\xec\x49\x89'.
  • + +
  • Python List String - Copies the bytes into the Python list format, for + example: [ 0x66, 0x2e, 0x0f, 0x1f, 0x84 ].
  • + +
  • C Array String - Copies the bytes into the C array format, for + example: { 0x66, 0x2e, 0x0f, 0x1f, 0x84 }.
  • +
  • Address - Copies the address at the top of the selection.
  • @@ -208,6 +217,17 @@
  • Byte String (No Spaces) - This is the same as the Byte String format, except there are no delimiting spaces between bytes.
  • + + +
  • Python Byte String - Copies the bytes into the Python byte string format, for + example: b'\x41\xec\x49\x89'.
  • + +
  • Python List String - Copies the bytes into the Python list format, for + example: [ 0x66, 0x2e, 0x0f, 0x1f, 0x84 ].
  • + +
  • C Array String - Copies the bytes into the C array format, for + example: { 0x66, 0x2e, 0x0f, 0x1f, 0x84 }.
  • +

    The Byte Viewer Bytes In Memory window can paste the following formats:

    diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/images/CopySpecial.png b/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/images/CopySpecial.png index 9e6ccc72b5..8cd9f18570 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/images/CopySpecial.png and b/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/images/CopySpecial.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/images/CopySpecialAgain.png b/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/images/CopySpecialAgain.png index 7f59b72d04..352de79ede 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/images/CopySpecialAgain.png and b/Ghidra/Features/Base/src/main/help/help/topics/ClipboardPlugin/images/CopySpecialAgain.png differ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/ClipboardPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/ClipboardPlugin.java index 805a18aada..0734f7e6ca 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/ClipboardPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/ClipboardPlugin.java @@ -377,7 +377,6 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl if (!clipboardService.isValidContext(context)) { return false; } - return clipboardService.canCopy(); } @@ -413,7 +412,8 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl } Clipboard systemClipboard = getSystemClipboard(); - return clipboardService.canPaste(getAvailableDataFlavors(systemClipboard)); + DataFlavor[] flavors = getAvailableDataFlavors(systemClipboard); + return clipboardService.canPaste(flavors); } @Override @@ -444,7 +444,6 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl if (!clipboardService.isValidContext(context)) { return false; } - return clipboardService.canCopySpecial(); } 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 0d4317b0f5..eb3e5a4c09 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,6 +48,7 @@ import ghidra.program.model.symbol.*; import ghidra.program.util.*; import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; +import util.CollectionUtils; public class CodeBrowserClipboardProvider extends ByteCopier implements ClipboardContentProviderService { @@ -77,25 +78,14 @@ public class CodeBrowserClipboardProvider extends ByteCopier list.add(COMMENTS_TYPE); list.add(BYTE_STRING_TYPE); list.add(BYTE_STRING_NO_SPACE_TYPE); + list.add(PYTHON_BYTE_STRING_TYPE); + list.add(PYTHON_LIST_TYPE); + list.add(CPP_BYTE_ARRAY_TYPE); list.add(ADDRESS_TEXT_TYPE); return list; } - private static final List PASTE_TYPES = createPasteTypesList(); - - private static List createPasteTypesList() { - List list = new LinkedList<>(); - - list.add(LABELS_COMMENTS_TYPE); - list.add(LABELS_TYPE); - list.add(COMMENTS_TYPE); - list.add(BYTE_STRING_TYPE); - list.add(BYTE_STRING_NO_SPACE_TYPE); - - return list; - } - protected boolean copyFromSelectionEnabled; protected ComponentProvider componentProvider; private ListingModel model; @@ -154,10 +144,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier else if (element.equals(COMMENTS_TYPE.getFlavor())) { return pasteLabelsComments(pasteData, false, true); } - else if (element.equals(BYTE_STRING_FLAVOR)) { - String data = (String) pasteData.getTransferData(BYTE_STRING_FLAVOR); - return pasteByteString(data); - } else if (element.equals(LabelStringTransferable.labelStringFlavor)) { return pasteLabelString(pasteData); } @@ -166,10 +152,8 @@ public class CodeBrowserClipboardProvider extends ByteCopier } } - // last ditch effort, try to paste as a byte string - String string = (String) pasteData.getTransferData(DataFlavor.stringFlavor); - if (string != null) { - return pasteByteString(string); + if (super.pasteBytes(pasteData)) { + return true; } tool.setStatusInfo("Paste failed: unsupported data type", true); @@ -178,36 +162,14 @@ public class CodeBrowserClipboardProvider extends ByteCopier String msg = e.getMessage(); if (msg == null) { msg = e.toString(); - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); } + + Msg.error(this, "Unexpected Exception: " + msg, e); tool.setStatusInfo("Paste failed: " + msg, true); } return false; } - @Override - protected boolean supportsPasteTransferable(Transferable transferable) { - DataFlavor[] flavors = transferable.getTransferDataFlavors(); - for (DataFlavor element : flavors) { - if (isPasteFlavorMatch(element, transferable)) { - return true; - } - } - return false; - } - - private boolean isPasteFlavorMatch(DataFlavor flavor, Transferable transferable) { - for (ClipboardType type : PASTE_TYPES) { - if (flavor.equals(type.getFlavor())) { - if (type == BYTE_STRING_TYPE) { // our parent handles validating bytes - return isValidBytesTransferable(transferable); - } - return true; - } - } - return false; - } - @Override public List getCurrentCopyTypes() { return COPY_TYPES; @@ -231,30 +193,8 @@ public class CodeBrowserClipboardProvider extends ByteCopier else if (copyType == COMMENTS_TYPE) { return copyLabelsComments(false, true); } - else if (copyType == BYTE_STRING_TYPE) { - String byteString = copyBytesAsString(getSelectedAddresses(), true, monitor); - return new ByteViewerTransferable(byteString); - } - else if (copyType == BYTE_STRING_NO_SPACE_TYPE) { - 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(); - notifyStateChanged(); + return copyBytes(copyType, monitor); } public void setStringContent(String text) { @@ -269,6 +209,12 @@ public class CodeBrowserClipboardProvider extends ByteCopier currentLocation = location; } + public void setSelection(ProgramSelection selection) { + currentSelection = selection; + copyFromSelectionEnabled = selection != null && !selection.isEmpty(); + notifyStateChanged(); + } + public void setProgram(Program p) { currentProgram = p; currentLocation = null; @@ -305,11 +251,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier } else if (currentLocation instanceof BytesFieldLocation) { // bytes are special--let them get copied and pasted as normal -// AddressSet addressSet = new AddressSet(currentProgram.getAddressFactory(), address); -// String byteString = -// copyBytesAsString(addressSet.getAddressRanges(), false, -// TaskMonitorAdapter.DUMMY_MONITOR); -// return new ByteViewerTransferable(byteString); return copyByteString(address); } else if (currentLocation instanceof OperandFieldLocation) { @@ -421,7 +362,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier private Transferable copyByteString(Address address) { AddressSet set = new AddressSet(address); - return copyBytes(set, false, TaskMonitor.DUMMY); + return createStringTransferable(copyBytesAsString(set, false, TaskMonitor.DUMMY)); } private CodeUnitInfoTransferable copyLabelsComments(boolean copyLabels, boolean copyComments) { @@ -433,13 +374,13 @@ public class CodeBrowserClipboardProvider extends ByteCopier return new CodeUnitInfoTransferable(list); } - @SuppressWarnings("unchecked") // assumed correct data; handled in exception case private boolean pasteLabelsComments(Transferable pasteData, boolean pasteLabels, boolean pasteComments) { try { - List list = (List) pasteData.getTransferData( + List list = (List) pasteData.getTransferData( CodeUnitInfoTransferable.localDataTypeFlavor); - Command cmd = new CodeUnitInfoPasteCmd(currentLocation.getAddress(), list, pasteLabels, + List infos = CollectionUtils.asList(list, CodeUnitInfo.class); + Command cmd = new CodeUnitInfoPasteCmd(currentLocation.getAddress(), infos, pasteLabels, pasteComments); return tool.execute(cmd, currentProgram); } @@ -476,36 +417,43 @@ public class CodeBrowserClipboardProvider extends ByteCopier return tool.execute(cmd, currentProgram); } else if (currentLocation instanceof OperandFieldLocation) { - OperandFieldLocation operandLocation = (OperandFieldLocation) currentLocation; - int opIndex = operandLocation.getOperandIndex(); - Listing listing = currentProgram.getListing(); - Instruction instruction = listing.getInstructionAt(operandLocation.getAddress()); - if (instruction == null) { - return false; - } + return pasteOperandField((OperandFieldLocation) currentLocation, labelName); + } - Reference reference = instruction.getPrimaryReference(opIndex); - if (reference == null) { - return false; - } + // try pasting onto something that is not a label + return maybePasteNonLabelString(labelName); + } - Variable var = currentProgram.getReferenceManager().getReferencedVariable(reference); - if (var != null) { - SetVariableNameCmd cmd = - new SetVariableNameCmd(var, labelName, SourceType.USER_DEFINED); - return tool.execute(cmd, currentProgram); - } + private boolean pasteOperandField(OperandFieldLocation operandLocation, String labelName) { - SymbolTable symbolTable = currentProgram.getSymbolTable(); - Symbol symbol = symbolTable.getSymbol(reference); - if ((symbol instanceof CodeSymbol) || (symbol instanceof FunctionSymbol)) { - String oldName = symbol.getName(); - Namespace namespace = symbol.getParentNamespace(); - Address symbolAddress = symbol.getAddress(); - RenameLabelCmd cmd = new RenameLabelCmd(symbolAddress, oldName, labelName, - namespace, SourceType.USER_DEFINED); - return tool.execute(cmd, currentProgram); - } + int opIndex = operandLocation.getOperandIndex(); + Listing listing = currentProgram.getListing(); + Instruction instruction = listing.getInstructionAt(operandLocation.getAddress()); + if (instruction == null) { + return false; + } + + Reference reference = instruction.getPrimaryReference(opIndex); + if (reference == null) { + return false; + } + + Variable var = currentProgram.getReferenceManager().getReferencedVariable(reference); + if (var != null) { + SetVariableNameCmd cmd = + new SetVariableNameCmd(var, labelName, SourceType.USER_DEFINED); + return tool.execute(cmd, currentProgram); + } + + SymbolTable symbolTable = currentProgram.getSymbolTable(); + Symbol symbol = symbolTable.getSymbol(reference); + if ((symbol instanceof CodeSymbol) || (symbol instanceof FunctionSymbol)) { + String oldName = symbol.getName(); + Namespace namespace = symbol.getParentNamespace(); + Address symbolAddress = symbol.getAddress(); + RenameLabelCmd cmd = new RenameLabelCmd(symbolAddress, oldName, labelName, + namespace, SourceType.USER_DEFINED); + return tool.execute(cmd, currentProgram); } // try pasting onto something that is not a label @@ -689,8 +637,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier return true; } if (flavor.equals(DataFlavor.stringFlavor)) { - // TODO: check if it is a valid hex string... - return true; + return true; // check if it is a valid hex string? } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/ClipboardContentProviderService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ClipboardContentProviderService.java index 1d407a6775..5c55a2c314 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/ClipboardContentProviderService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ClipboardContentProviderService.java @@ -15,9 +15,6 @@ */ package ghidra.app.services; -import ghidra.app.util.ClipboardType; -import ghidra.util.task.TaskMonitor; - import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.util.List; @@ -26,21 +23,26 @@ import javax.swing.event.ChangeListener; import docking.ActionContext; import docking.ComponentProvider; +import ghidra.app.util.ClipboardType; +import ghidra.util.task.TaskMonitor; /** - * ClipboardContentProvider determines what types of - * transfer data can be placed on the clipboard, and cut, copy, and paste. + * Determines what types of transfer data can be placed on the clipboard, as well as if + * cut, copy, and paste operations are supported */ public interface ClipboardContentProviderService { /** - * Returns the component provider associated with this - * ClipboardContentProviderService. + * Returns the component provider associated with this service + * @return the provider */ public ComponentProvider getComponentProvider(); /** * Triggers the default copy operation + * @param monitor monitor that shows progress of the copy to clipboard, and + * may be canceled + * @return the created transferable; null if the copy was unsuccessful */ public Transferable copy(TaskMonitor monitor); @@ -49,44 +51,49 @@ public interface ClipboardContentProviderService { * @param copyType contains the data flavor of the clipboard contents * @param monitor monitor that shows progress of the copy to clipboard, and * may be canceled + * @return the created transferable; null if the copy was unsuccessful */ public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor); /** * Triggers the default paste operation for the given transferable + * @param pasteData the paste transferable + * @return true of the paste was successful */ public boolean paste(Transferable pasteData); /** * Gets the currently active ClipboardTypes for copying with the current context + * @return the types */ public List getCurrentCopyTypes(); /** * Return whether the given context is valid for actions on popup menus. * @param context the context of where the popup menu will be positioned. + * @return true if valid */ public boolean isValidContext(ActionContext context); - // TODO needs updating. Assumed copyAddToPopup became copy(TaskMonitor monitor) /** * Returns true if copy should be enabled; false if it should be disabled. This method can * be used in conjunction with {@link #copy(TaskMonitor)} in order to add menu items to * popup menus but to have them enabled when appropriate. + * @return true if copy should be enabled */ public boolean enableCopy(); /** * Returns true if copySpecial actions should be enabled; - * @return + * @return true if copySpecial actions should be enabled; */ public boolean enableCopySpecial(); - // TODO needs updating. Assumed pasteAddToPopup became paste(Transferable pasteData) /** * Returns true if paste should be enabled; false if it should be disabled. This method can * be used in conjunction with {@link #paste(Transferable)} in order to add menu items to * popup menus but to have them enabled when appropriate. + * @return true if paste should be enabled */ public boolean enablePaste(); @@ -130,6 +137,7 @@ public interface ClipboardContentProviderService { /** * Returns true if the given service provider can currently perform a 'copy special' * operation. + * @return true if copy special is enabled */ public boolean canCopySpecial(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java index 7bec236ec0..691249e6e2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java @@ -18,6 +18,8 @@ package ghidra.app.util; import java.awt.datatransfer.*; import java.io.IOException; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import docking.dnd.GenericDataFlavor; import docking.dnd.StringTransferable; @@ -25,8 +27,7 @@ import docking.widgets.OptionDialog; import ghidra.framework.cmd.Command; import ghidra.framework.model.DomainObject; import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryAccessException; @@ -46,12 +47,36 @@ public abstract class ByteCopier { public static DataFlavor BYTE_STRING_FLAVOR = createByteStringLocalDataTypeFlavor(); public static DataFlavor BYTE_STRING_NO_SPACES_FLAVOR = createByteStringNoSpacesLocalDataTypeFlavor(); + public static DataFlavor PYTHON_BYTE_STRING_FLAVOR = + createPythonByteStringLocalDataTypeFlavor(); + public static DataFlavor PYTHON_LIST_FLAVOR = createPythonListLocalDataTypeFlavor(); + public static DataFlavor CPP_BYTE_ARRAY_FLAVOR = + createCppByteArrayLocalDataTypeFlavor(); protected static final List EMPTY_LIST = Collections.emptyList(); public static final ClipboardType BYTE_STRING_TYPE = new ClipboardType(BYTE_STRING_FLAVOR, "Byte String"); public static final ClipboardType BYTE_STRING_NO_SPACE_TYPE = new ClipboardType(BYTE_STRING_NO_SPACES_FLAVOR, "Byte String (No Spaces)"); + public static final ClipboardType PYTHON_BYTE_STRING_TYPE = + new ClipboardType(PYTHON_BYTE_STRING_FLAVOR, "Python Byte String"); + public static final ClipboardType PYTHON_LIST_TYPE = + new ClipboardType(PYTHON_LIST_FLAVOR, "Python List"); + public static final ClipboardType CPP_BYTE_ARRAY_TYPE = + new ClipboardType(CPP_BYTE_ARRAY_FLAVOR, "C Array"); + + private static final Map PROGRAMMING_PATTERNS_BY_FLAVOR = + Map.of( + PYTHON_BYTE_STRING_FLAVOR, Pattern.compile("b'(.*)'"), + PYTHON_LIST_FLAVOR, Pattern.compile("\\[(.*)\\]"), + CPP_BYTE_ARRAY_FLAVOR, Pattern.compile("\\{(.*)\\}")); + + /** + * Pattern to recognize bytes that have been encoded during a copy operation using one of this + * class's programming copy types + */ + private static final Pattern PROGRAMMING_BYTES_PATTERN = + Pattern.compile("(?:\\\\x|0x)([a-fA-F0-9]{2})"); private static DataFlavor createByteStringLocalDataTypeFlavor() { @@ -61,7 +86,7 @@ public abstract class ByteCopier { "Local flavor--byte string with spaces"); } catch (Exception e) { - Msg.showError(ByteCopier.class, null, "Could Not Create Data Flavor", + Msg.error(ByteCopier.class, "Unexpected exception creating data flavor for byte string", e); } @@ -76,13 +101,58 @@ public abstract class ByteCopier { "Local flavor--byte string with NO spaces"); } catch (Exception e) { - Msg.showError(ByteCopier.class, null, "Could Not Create Data Flavor", + Msg.error(ByteCopier.class, "Unexpected exception creating data flavor for byte string with no spaces", e); } return null; } + private static DataFlavor createPythonByteStringLocalDataTypeFlavor() { + + try { + return new GenericDataFlavor( + DataFlavor.javaJVMLocalObjectMimeType + "; class=java.lang.String", + "Local flavor--Python byte string"); + } + catch (Exception e) { + Msg.error(ByteCopier.class, + "Unexpected exception creating data flavor for Python byte string", e); + } + + return null; + } + + private static DataFlavor createPythonListLocalDataTypeFlavor() { + + try { + return new GenericDataFlavor( + DataFlavor.javaJVMLocalObjectMimeType + "; class=java.lang.String", + "Local flavor--Python list"); + } + catch (Exception e) { + Msg.error(ByteCopier.class, + "Unexpected exception creating data flavor for Python list", e); + } + + return null; + } + + private static DataFlavor createCppByteArrayLocalDataTypeFlavor() { + + try { + return new GenericDataFlavor( + DataFlavor.javaJVMLocalObjectMimeType + "; class=java.lang.String", + "Local flavor--C++ array"); + } + catch (Exception e) { + Msg.error(ByteCopier.class, + "Unexpected exception creating data flavor for C array", e); + } + + return null; + } + protected PluginTool tool; protected Program currentProgram; protected ProgramSelection currentSelection; @@ -92,8 +162,12 @@ public abstract class ByteCopier { // limit construction } - protected Transferable copyBytes(boolean includeSpaces, TaskMonitor monitor) { - return copyBytes(currentSelection, includeSpaces, monitor); + protected AddressSetView getSelectedAddresses() { + AddressSetView addressSet = currentSelection; + if (addressSet == null || addressSet.isEmpty()) { + return new AddressSet(currentLocation.getAddress()); + } + return currentSelection; } protected Transferable copyBytes(AddressSetView addresses, boolean includeSpaces, @@ -104,42 +178,18 @@ public abstract class ByteCopier { protected String copyBytesAsString(AddressSetView addresses, boolean includeSpaces, TaskMonitor monitor) { - Memory memory = currentProgram.getMemory(); String delimiter = includeSpaces ? " " : ""; + return copyBytesAsString(addresses, delimiter, monitor); + } + + protected String copyBytesAsString(AddressSetView addresses, String delimiter, + TaskMonitor monitor) { + + Memory memory = currentProgram.getMemory(); ByteIterator bytes = new ByteIterator(addresses, memory); return NumericUtilities.convertBytesToString(bytes, delimiter); } - protected boolean supportsPasteTransferable(Transferable transferable) { - return isValidBytesTransferable(transferable); - } - - protected boolean isValidBytesTransferable(Transferable transferable) { - - DataFlavor[] flavors = transferable.getTransferDataFlavors(); - for (DataFlavor element : flavors) { - - try { - Object object = transferable.getTransferData(element); - if (object instanceof String) { - String string = (String) object; - if (!isOnlyAsciiBytes(string)) { - tool.setStatusInfo("Paste string contains non-text ascii bytes. " + - "Only the ascii text will be pasted.", true); - - string = keepOnlyAsciiBytes(string); - } - return (getBytes(string) != null); - } - } - catch (Exception e) { - // don't care; try the next one - } - } - - return false; - } - private byte[] getBytes(String transferString) { byte[] bytes = getHexBytes(transferString); @@ -215,27 +265,121 @@ public abstract class ByteCopier { return null; } + protected Transferable copyBytes(ClipboardType copyType, TaskMonitor monitor) { + + if (copyType == BYTE_STRING_TYPE) { + String byteString = copyBytesAsString(getSelectedAddresses(), true, monitor); + return new ByteStringTransferable(byteString); + } + else if (copyType == BYTE_STRING_NO_SPACE_TYPE) { + String byteString = copyBytesAsString(getSelectedAddresses(), false, monitor); + return new ByteStringTransferable(byteString); + } + else if (copyType == PYTHON_BYTE_STRING_TYPE) { + String prefix = "\\x"; + String bs = copyBytesAsString(getSelectedAddresses(), prefix, monitor); + String fixed = "b'" + prefix + bs + "'"; + return new ProgrammingByteStringTransferable(fixed, copyType.getFlavor()); + } + else if (copyType == PYTHON_LIST_TYPE) { + String prefix = "0x"; + String bs = copyBytesAsString(getSelectedAddresses(), ", " + prefix, monitor); + String fixed = "[ " + prefix + bs + " ]"; + return new ProgrammingByteStringTransferable(fixed, copyType.getFlavor()); + } + else if (copyType == CPP_BYTE_ARRAY_TYPE) { + String prefix = "0x"; + String bs = copyBytesAsString(getSelectedAddresses(), ", " + prefix, monitor); + String byteString = "{ " + prefix + bs + " }"; + return new ProgrammingByteStringTransferable(byteString, copyType.getFlavor()); + } + + return null; + } + protected boolean pasteBytes(Transferable pasteData) throws UnsupportedFlavorException, IOException { - if (!supportsPasteTransferable(pasteData)) { - tool.setStatusInfo("Paste failed: No valid data on clipboard", true); + + DataFlavor[] flavors = pasteData.getTransferDataFlavors(); + DataFlavor byteStringFlavor = getByteStringFlavor(flavors); + if (byteStringFlavor != null) { + String data = (String) pasteData.getTransferData(byteStringFlavor); + return pasteByteString(data); + } + + DataFlavor programmingFlavor = getProgrammingFlavor(flavors); + if (programmingFlavor != null) { + String data = (String) pasteData.getTransferData(programmingFlavor); + String byteString = extractProgrammingBytes(programmingFlavor, data); + if (byteString != null) { + return pasteByteString(byteString); + } + } + + if (!pasteData.isDataFlavorSupported(DataFlavor.stringFlavor)) { + tool.setStatusInfo("Paste failed: unsupported data type", true); return false; } - if (pasteData.isDataFlavorSupported(BYTE_STRING_FLAVOR)) { - String data = (String) pasteData.getTransferData(BYTE_STRING_FLAVOR); - return pasteByteString(data); - } - - if (pasteData.isDataFlavorSupported(BYTE_STRING_NO_SPACES_FLAVOR)) { - String data = (String) pasteData.getTransferData(BYTE_STRING_NO_SPACES_FLAVOR); - return pasteByteString(data); - } - + // see if the pasted data is similar to other known programming formats String string = (String) pasteData.getTransferData(DataFlavor.stringFlavor); + if (string == null) { + tool.setStatusInfo("Paste failed: no string data", true); + return false; + } + return pasteByteString(string); } + private DataFlavor getProgrammingFlavor(DataFlavor[] flavors) { + for (DataFlavor flavor : flavors) { + if (flavor.equals(PYTHON_BYTE_STRING_FLAVOR) || + flavor.equals(PYTHON_LIST_FLAVOR) || + flavor.equals(CPP_BYTE_ARRAY_FLAVOR)) { + return flavor; + } + } + return null; + } + + private DataFlavor getByteStringFlavor(DataFlavor[] flavors) { + + for (DataFlavor flavor : flavors) { + if (flavor.equals(BYTE_STRING_FLAVOR) || + flavor.equals(BYTE_STRING_NO_SPACES_FLAVOR)) { + return flavor; + } + } + + return null; + } + + private String extractProgrammingBytes(DataFlavor flavor, String data) { + + Pattern pattern = PROGRAMMING_PATTERNS_BY_FLAVOR.get(flavor); + Matcher matcher = pattern.matcher(data); + if (!matcher.matches()) { + return null; + } + + String bytes = matcher.group(1); + if (bytes == null) { + return null; + } + + Matcher bytesMatcher = PROGRAMMING_BYTES_PATTERN.matcher(bytes); + if (!bytesMatcher.find()) { + return null; + } + + StringBuilder buffy = new StringBuilder(); + buffy.append(bytesMatcher.group(1)); + while (bytesMatcher.find()) { + buffy.append(bytesMatcher.group(1)); + } + return buffy.toString(); + } + protected boolean pasteByteString(final String string) { Command cmd = new Command() { @@ -243,75 +387,100 @@ public abstract class ByteCopier { @Override public boolean applyTo(DomainObject domainObject) { - if (domainObject instanceof Program) { - String validString = string; - if (!isOnlyAsciiBytes(string)) { - tool.setStatusInfo("Pasted string contained non-text ascii bytes. " + - "Only the ascii text was pasted.", true); + if (!(domainObject instanceof Program)) { + return false; + } - validString = keepOnlyAsciiBytes(string); + String validString = string; + if (!isOnlyAsciiBytes(string)) { + tool.setStatusInfo("Pasted string contained non-text ascii bytes. " + + "Only the ascii will be used.", true); + validString = keepOnlyAsciiBytes(string); + } + + byte[] bytes = getBytes(validString); + if (bytes == null) { + status = "Improper data format. Expected sequence of hex bytes"; + tool.beep(); + return false; + } + + // Ensure that we are not writing over instructions + Program program = (Program) domainObject; + Address address = currentLocation.getAddress(); + if (!hasEnoughSpace(program, address, bytes.length)) { + status = + "Not enough space to paste all bytes. Encountered data or instructions."; + tool.beep(); + return false; + } + + // Ask the user before pasting a string into the program. Since having a string in + // the clipboard is so common, this is to prevent an accidental paste. + if (!confirmPaste(validString)) { + return true; // the user cancelled; the command is successful + } + + boolean pastedAllBytes = pasteBytes(program, bytes); + if (!pastedAllBytes) { + tool.setStatusInfo("Not all bytes were pasted due to memory access issues", + true); + } + + return true; + } + + private boolean pasteBytes(Program program, byte[] bytes) { + + // note: loop one byte at a time here, since Memory will validate all addresses + // before pasting any bytes + boolean foundError = false; + Address address = currentLocation.getAddress(); + Memory memory = program.getMemory(); + for (byte element : bytes) { + try { + memory.setByte(address, element); } + catch (MemoryAccessException e) { + // Keep trying the remaining bytes. Should we just stop in this case? + foundError = true; + } + address = address.next(); + } + return foundError; + } - byte[] bytes = getBytes(validString); - if (bytes == null) { - status = "Improper data format (expected sequence of hex bytes)"; + private boolean confirmPaste(String validString) { + + // create a truncated version of the string to show in the dialog + String partialText = validString.length() < 40 ? validString + : validString.substring(0, 40) + " ..."; + int result = OptionDialog.showYesNoDialog(null, "Paste String Into Program?", + "Are you sure you want to paste the string \"" + partialText + + "\"\n into the program's memory?"); + + return result != OptionDialog.NO_OPTION; + } + + private boolean hasEnoughSpace(Program program, Address address, int byteCount) { + Listing listing = program.getListing(); + for (int i = 0; i < byteCount;) { + if (address == null) { + status = "Not enough addresses to paste bytes"; tool.beep(); return false; } - - // Ensure that we are not writing over instructions - Program curProgram = (Program) domainObject; - Listing listing = curProgram.getListing(); - Address curAddr = currentLocation.getAddress(); - int byteCount = bytes.length; - for (int i = 0; i < byteCount;) { - if (curAddr == null) { - status = "Not enough addresses to paste bytes"; - tool.beep(); - return false; - } - CodeUnit curCodeUnit = listing.getCodeUnitContaining(curAddr); - if (!(curCodeUnit instanceof Data) || ((Data) curCodeUnit).isDefined()) { - status = "Cannot paste on top of defined instructions/data"; - tool.beep(); - return false; - } - int length = curCodeUnit.getLength(); - i += length; - curAddr = curCodeUnit.getMaxAddress().next(); + CodeUnit codeUnit = listing.getCodeUnitContaining(address); + if (!(codeUnit instanceof Data) || ((Data) codeUnit).isDefined()) { + status = "Cannot paste on top of defined instructions/data"; + tool.beep(); + return false; } - - // Per SCR 11212, ask the user before pasting a string into the program. - // Since having a string in the clipboard is so common, this is to prevent - // an accidental paste. - - // create a truncated version of the string to show in the dialog - String partialText = validString.length() < 40 ? validString - : validString.substring(0, 40) + " ..."; - - int result = OptionDialog.showYesNoDialog(null, "Paste String Into Program?", - "Are you sure you want to paste the string \"" + partialText + - "\"\n into the program's memory?"); - - if (result == OptionDialog.NO_OPTION) { - return true; - } - - // Write data - curAddr = currentLocation.getAddress(); - for (byte element : bytes) { - try { - curProgram.getMemory().setByte(curAddr, element); - } - catch (MemoryAccessException e1) { - // handle below - } - curAddr = curAddr.next(); - } - - return true; + int length = codeUnit.getLength(); + i += length; + address = codeUnit.getMaxAddress().next(); } - return false; + return true; } @Override @@ -389,31 +558,69 @@ public abstract class ByteCopier { } } - public static class ByteViewerTransferable implements Transferable { + public static class ProgrammingByteStringTransferable implements Transferable { - private final DataFlavor[] flavors = { BYTE_STRING_NO_SPACE_TYPE.getFlavor(), - BYTE_STRING_TYPE.getFlavor(), DataFlavor.stringFlavor }; - private final List flavorList = Arrays.asList(flavors); + private List flavorList; + private DataFlavor[] flavors; + private DataFlavor programmingFlavor; + private String byteString; - private final String byteString; - - private final String byteViewerRepresentation; - - public ByteViewerTransferable(String byteString) { - this(byteString, null); - } - - public ByteViewerTransferable(String byteString, String byteViewerRepresentation) { + public ProgrammingByteStringTransferable(String byteString, DataFlavor flavor) { this.byteString = byteString; - this.byteViewerRepresentation = byteViewerRepresentation; + this.programmingFlavor = flavor; + this.flavors = new DataFlavor[] { flavor, DataFlavor.stringFlavor }; + this.flavorList = Arrays.asList(flavors); } @Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if (flavor.equals(DataFlavor.stringFlavor)) { - if (byteViewerRepresentation != null) { - return byteViewerRepresentation; + return byteString; // just default to the byte string when no 'special' string data + } + if (flavor.equals(programmingFlavor)) { + return byteString; + } + throw new UnsupportedFlavorException(flavor); + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return flavors; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return flavorList.contains(flavor); + } + } + + public static class ByteStringTransferable implements Transferable { + + private final DataFlavor[] flavors = { + BYTE_STRING_NO_SPACE_TYPE.getFlavor(), + BYTE_STRING_TYPE.getFlavor(), + DataFlavor.stringFlavor }; + private final List flavorList = Arrays.asList(flavors); + + private final String byteString; + private final String stringRepresentation; + + public ByteStringTransferable(String byteString) { + this(byteString, null); + } + + public ByteStringTransferable(String byteString, String stringRepresentation) { + this.byteString = byteString; + this.stringRepresentation = stringRepresentation; + } + + @Override + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, IOException { + if (flavor.equals(DataFlavor.stringFlavor)) { + if (stringRepresentation != null) { + return stringRepresentation; } return byteString; // just default to the byte string when no 'special' string data } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/ClipboardType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/ClipboardType.java index 63506dff8f..83baa67dfa 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/ClipboardType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/ClipboardType.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +21,7 @@ import java.awt.datatransfer.DataFlavor; * Defines a "type" for items in the Clipboard */ public class ClipboardType { - + private DataFlavor flavor; private String typeName; @@ -35,26 +34,25 @@ public class ClipboardType { this.flavor = flavor; this.typeName = typeName; } - + /** - * Returns the DataFlavor for this ClipboardType + * Returns the DataFlavor for this type + * @return the flavor */ public DataFlavor getFlavor() { return flavor; } /** - * Returns the name of this Clipboard Type. + * Returns the name of this type + * @return the name */ public String getTypeName() { return typeName; } - /** - * @see java.lang.Object#toString() - */ @Override - public String toString() { + public String toString() { return typeName; } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProviderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProviderTest.java new file mode 100644 index 0000000000..8d6d698144 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProviderTest.java @@ -0,0 +1,224 @@ +/* ### + * 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.app.plugin.core.clipboard; + +import static org.hamcrest.core.IsInstanceOf.*; +import static org.junit.Assert.*; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; + +import org.junit.Before; +import org.junit.Test; + +import docking.dnd.StringTransferable; +import docking.widgets.OptionDialog; +import ghidra.app.util.ByteCopier; +import ghidra.app.util.ByteCopier.ProgrammingByteStringTransferable; +import ghidra.app.util.ClipboardType; +import ghidra.framework.cmd.Command; +import ghidra.framework.model.DomainObject; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.DummyTool; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.AssertException; +import ghidra.util.task.TaskMonitor; + +public class CodeBrowserClipboardProviderTest extends AbstractGhidraHeadedIntegrationTest { + + private Program program; + private CodeBrowserClipboardProvider clipboardProvider; + + @Before + public void setUp() throws Exception { + + program = createProgram(); + PluginTool tool = new DummyTool() { + @Override + public boolean execute(Command command, DomainObject obj) { + boolean result = command.applyTo(obj); + if (!result) { + throw new AssertException("Failed to write bytes"); + } + return true; + } + }; + + clipboardProvider = new CodeBrowserClipboardProvider(tool, null); + clipboardProvider.setProgram(program); + } + + private Program createProgram() throws Exception { + ProgramBuilder builder = new ProgramBuilder("default", ProgramBuilder._TOY, this); + + builder.createMemory("test", "0x01001050", 20000); + + builder.setBytes("0x01001050", + "0e 5e f4 77 33 58 f4 77 91 45 f4 77 88 7c f4 77 8d 70 f5 77 05 62 f4 77 f0 a3 " + + "f4 77 09 56 f4 77 10 17 f4 77 f7 29 f6 77 02 59 f4 77"); + + builder.setBytes("0x01002050", "00 00 00 00 00 00 00 00 00 00 00 00 00"); + + builder.createMemoryReference("0x01002cc0", "0x01002cf0", RefType.DATA, + SourceType.USER_DEFINED); + builder.createMemoryReference("0x01002d04", "0x01002d0f", RefType.DATA, + SourceType.USER_DEFINED); + + DataType dt = DataType.DEFAULT; + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + builder.createEmptyFunction("ghidra", "0x01002cf5", 1, dt, p); + builder.createEmptyFunction("sscanf", "0x0100415a", 1, dt, p); + + builder.setBytes("0x0100418c", "ff 15 08 10 00 01"); + builder.disassemble("0x0100418c", 6); + + return builder.getProgram(); + } + + @Test + public void testCopyPasteSpecial_PythonByteString() throws Exception { + + int length = 4; + clipboardProvider.setSelection(selection("0x01001050", length)); + ClipboardType type = ByteCopier.PYTHON_BYTE_STRING_TYPE; + Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY); + assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class)); + + String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor); + assertEquals("b'\\x0e\\x5e\\xf4\\x77'", byteString); + + String pasteAddress = "0x01002050"; + paste(pasteAddress, transferable); + assertBytesAt(pasteAddress, "0e 5e f4 77", length); + } + + @Test + public void testCopyPasteSpecial_PythonListString() throws Exception { + + int length = 4; + clipboardProvider.setSelection(selection("0x01001050", 4)); + ClipboardType type = ByteCopier.PYTHON_LIST_TYPE; + Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY); + assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class)); + + String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor); + assertEquals("[ 0x0e, 0x5e, 0xf4, 0x77 ]", byteString); + + String pasteAddress = "0x01002050"; + paste(pasteAddress, transferable); + assertBytesAt(pasteAddress, "0e 5e f4 77", length); + } + + @Test + public void testCopyPasteSpecial_CppByteArray() throws Exception { + + int length = 4; + clipboardProvider.setSelection(selection("0x01001050", 4)); + ClipboardType type = ByteCopier.CPP_BYTE_ARRAY_TYPE; + Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY); + assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class)); + + String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor); + assertEquals("{ 0x0e, 0x5e, 0xf4, 0x77 }", byteString); + + String pasteAddress = "0x01002050"; + paste(pasteAddress, transferable); + assertBytesAt(pasteAddress, "0e 5e f4 77", length); + } + + @Test + public void testCopyPaste_ByteString() throws Exception { + + String byteString = "0e 5e f4 77"; + StringTransferable transferable = new StringTransferable(byteString); + + String pasteAddress = "0x01002050"; + paste(pasteAddress, transferable); + assertBytesAt(pasteAddress, byteString, 4); + } + + @Test + public void testCopyPaste_ByteString_MixedWithNonAscii() throws Exception { + + // the byte string contains ascii and non-ascii + String byteString = + "0e " + ((char) 0x80) + " 5e " + ((char) 0x81 + " f4 " + ((char) 0x82)) + " 77"; + String asciiByteString = "0e 5e f4 77"; + StringTransferable transferable = new StringTransferable(byteString); + + String pasteAddress = "0x01002050"; + paste(pasteAddress, transferable); + assertBytesAt(pasteAddress, asciiByteString, 4); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private void paste(String address, Transferable transferable) { + + tx(program, () -> { + doPaste(address, transferable); + }); + } + + private void doPaste(String address, Transferable transferable) { + clipboardProvider.setLocation(location(address)); + runSwing(() -> clipboardProvider.paste(transferable), false); + + OptionDialog confirmDialog = waitForDialogComponent(OptionDialog.class); + pressButtonByText(confirmDialog, "Yes"); + + waitForTasks(); + program.flushEvents(); + waitForSwing(); + } + + private void assertBytesAt(String address, String bytes, int length) + throws MemoryAccessException { + Memory memory = program.getMemory(); + byte[] memoryBytes = new byte[length]; + memory.getBytes(addr(address), memoryBytes, 0, length); + + String memoryByteString = NumericUtilities.convertBytesToString(memoryBytes, " "); + assertEquals(bytes, memoryByteString); + } + + private ProgramSelection selection(String addressString, int n) { + Address address = addr(addressString); + AddressSetView addresses = new AddressSet(address, address.add(n - 1)); + return new ProgramSelection(addresses); + } + + private Address addr(String addr) { + return program.getAddressFactory().getAddress(addr); + } + + private ProgramLocation location(String addressString) { + return new ProgramLocation(program, addr(addressString)); + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteCommentsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteCommentsTest.java index 394290918f..bfe29d65b0 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteCommentsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteCommentsTest.java @@ -132,6 +132,7 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { pmTwo.openProgram(df2); programTwo = (ProgramDB) pmTwo.getCurrentProgram(); }); + } @Test @@ -145,7 +146,8 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { goTo(toolOne, 0x32a); ClipboardPlugin plugin = getPlugin(toolOne, ClipboardPlugin.class); - DockingActionIf pasteAction = getAction(plugin, "Paste"); + ClipboardContentProviderService service = getClipboardService(plugin); + DockingActionIf pasteAction = getLocalAction(service, "Paste", plugin); assertEnabled(pasteAction, cb.getProvider()); } @@ -194,8 +196,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { // in Program One, add MyLabel at 032a int transactionID = programOne.startTransaction("test"); - programOne.getSymbolTable().createLabel(addr(programOne, 0x032a), "MyLabel", - SourceType.USER_DEFINED); + programOne.getSymbolTable() + .createLabel(addr(programOne, 0x032a), "MyLabel", + SourceType.USER_DEFINED); programOne.endTransaction(transactionID, true); goTo(toolTwo, 0x0326); @@ -385,8 +388,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { programOne.getSymbolTable().getSymbol("LAB_0331", addr(programOne, 0x0331), null); // in Browser(1) change default label at 331 to JUNK int transactionID = programOne.startTransaction("test"); - programOne.getSymbolTable().createLabel(addr(programOne, 0x0331), "JUNK", - SourceType.USER_DEFINED); + programOne.getSymbolTable() + .createLabel(addr(programOne, 0x0331), "JUNK", + SourceType.USER_DEFINED); programOne.endTransaction(transactionID, true); // // in Browser(1) go to 331 @@ -426,8 +430,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { public void testPasteAtMultipleLabels() throws Exception { // in program 2, create a second label, JUNK2, at 0331 int transactionID = programOne.startTransaction("test"); - programOne.getSymbolTable().createLabel(addr(programOne, 0x331), "JUNK2", - SourceType.USER_DEFINED); + programOne.getSymbolTable() + .createLabel(addr(programOne, 0x331), "JUNK2", + SourceType.USER_DEFINED); programOne.endTransaction(transactionID, true); // in Browser(2) select 331 through 334, contains "RSR10" @@ -474,8 +479,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { @Test public void testPasteWhereUserLabelExists() throws Exception { int transactionID = programOne.startTransaction("test"); - programOne.getSymbolTable().createLabel(addr(programOne, 0x331), "JUNK2", - SourceType.USER_DEFINED); + programOne.getSymbolTable() + .createLabel(addr(programOne, 0x331), "JUNK2", + SourceType.USER_DEFINED); programOne.endTransaction(transactionID, true); // in Browser(2) select 331 through 334, contains "RSR10" @@ -586,13 +592,19 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { cb.goToField(addr(programOne, 0x0331), LabelFieldFactory.FIELD_NAME, 0, 0); f = (ListingTextField) cb.getCurrentField(); - assertEquals(programOne.getSymbolTable().getSymbol("LAB_00000331", addr(programOne, 0x0331), - null).getName(), f.getText()); + assertEquals(programOne.getSymbolTable() + .getSymbol("LAB_00000331", addr(programOne, 0x0331), + null) + .getName(), + f.getText()); cb.goToField(addr(programOne, 0x031b), LabelFieldFactory.FIELD_NAME, 0, 0); f = (ListingTextField) cb.getCurrentField(); - assertEquals(programOne.getSymbolTable().getSymbol("LAB_0000031b", addr(programOne, 0x031b), - null).getName(), f.getText()); + assertEquals(programOne.getSymbolTable() + .getSymbol("LAB_0000031b", addr(programOne, 0x031b), + null) + .getName(), + f.getText()); redo(programOne); @@ -616,8 +628,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { // create a function over the range 0x31b through 0x0343. int transactionID = programOne.startTransaction("test"); String name = SymbolUtilities.getDefaultFunctionName(min); - programOne.getListing().createFunction(name, min, new AddressSet(min, max), - SourceType.USER_DEFINED); + programOne.getListing() + .createFunction(name, min, new AddressSet(min, max), + SourceType.USER_DEFINED); programOne.endTransaction(transactionID, true); programOne.flushEvents(); waitForSwing(); @@ -710,19 +723,23 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { private void copyToolTwoLabels() { ClipboardPlugin plugin = getPlugin(toolTwo, ClipboardPlugin.class); ClipboardContentProviderService service = - getCodeBrowserClipboardContentProviderService(plugin); + getClipboardService(plugin); DockingAction action = getLocalAction(service, "Copy Special", plugin); assertNotNull(action); assertEnabled(action, cb2.getProvider()); - plugin.copySpecial(service, CodeBrowserClipboardProvider.LABELS_COMMENTS_TYPE); + runSwing( + () -> plugin.copySpecial(service, CodeBrowserClipboardProvider.LABELS_COMMENTS_TYPE)); } private void pasteToolOne() { + ClipboardPlugin plugin = getPlugin(toolOne, ClipboardPlugin.class); - DockingActionIf pasteAction = getAction(plugin, "Paste"); + ClipboardContentProviderService service = getClipboardService(plugin); + DockingActionIf pasteAction = getLocalAction(service, "Paste", plugin); assertEnabled(pasteAction, cb.getProvider()); performAction(pasteAction, true); + waitForSwing(); } private void setupTool(PluginTool tool) throws Exception { @@ -809,13 +826,13 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { waitForSwing(); } - private ClipboardContentProviderService getCodeBrowserClipboardContentProviderService( + private ClipboardContentProviderService getClipboardService( ClipboardPlugin clipboardPlugin) { Map serviceMap = (Map) getInstanceField("serviceActionMap", clipboardPlugin); Set keySet = serviceMap.keySet(); for (Object name : keySet) { ClipboardContentProviderService service = (ClipboardContentProviderService) name; - if (service instanceof CodeBrowserClipboardProvider) { + if (service.getClass().equals(CodeBrowserClipboardProvider.class)) { return service; } } @@ -825,17 +842,12 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { @SuppressWarnings("unchecked") private DockingAction getLocalAction(ClipboardContentProviderService service, String actionName, ClipboardPlugin clipboardPlugin) { - Map serviceMap = (Map) getInstanceField("serviceActionMap", clipboardPlugin); - Set keySet = serviceMap.keySet(); - for (Object name : keySet) { - ClipboardContentProviderService currentService = (ClipboardContentProviderService) name; - if (currentService == service) { - List actionList = (List) serviceMap.get(service); - for (DockingAction pluginAction : actionList) { - if (pluginAction.getName().equals(actionName)) { - return pluginAction; - } - } + Map actionsByService = + (Map) getInstanceField("serviceActionMap", clipboardPlugin); + List actionList = (List) actionsByService.get(service); + for (DockingAction pluginAction : actionList) { + if (pluginAction.getName().equals(actionName)) { + return pluginAction; } } @@ -844,7 +856,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest { private void assertEnabled(DockingActionIf action, ComponentProvider provider) { boolean isEnabled = - runSwing(() -> action.isEnabledForContext(provider.getActionContext(null))); - assertTrue(isEnabled); + runSwing(() -> { + return action.isEnabledForContext(provider.getActionContext(null)); + }); + assertTrue("Action was not enabled when it should be", isEnabled); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteFunctionInfoTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteFunctionInfoTest.java index 88926fe381..71afa91362 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteFunctionInfoTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteFunctionInfoTest.java @@ -15,8 +15,7 @@ */ package ghidra.app.plugin.core.clipboard; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.awt.Point; import java.awt.event.MouseEvent; @@ -26,6 +25,7 @@ import javax.swing.SwingUtilities; import org.junit.*; +import docking.action.DockingAction; import docking.action.DockingActionIf; import docking.widgets.fieldpanel.FieldPanel; import ghidra.app.cmd.function.SetVariableCommentCmd; @@ -53,11 +53,8 @@ import ghidra.program.util.ProgramSelection; import ghidra.test.*; /** - * Test copy/paste function information. - * - * + * Test copy/paste function information */ - public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTest { private TestEnv env; @@ -72,14 +69,6 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe private Options fieldOptions2; private CodeBrowserPlugin cb1; - /** - * Constructor for CopyPasteFunctionInfoTest. - * @param arg0 - */ - public CopyPasteFunctionInfoTest() { - super(); - } - private Program buildNotepad(String name) throws Exception { ToyProgramBuilder builder = new ToyProgramBuilder(name, true, ProgramBuilder._TOY); builder.createMemory("test1", "0x01001000", 0x8000); @@ -133,9 +122,6 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe resetOptions(); } - /* - * @see TestCase#tearDown() - */ @After public void tearDown() throws Exception { env.dispose(); @@ -174,12 +160,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe goToAddr(toolTwo, 0x1004700); click2(); - // paste - plugin = getPlugin(toolTwo, ClipboardPlugin.class); - DockingActionIf pasteAction = getAction(plugin, "Paste"); - assertEnabled(pasteAction); - performAction(pasteAction, true); - waitForSwing(); + paste(toolTwo); // function FUN_01004700 should be renamed to "ghidra" CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class); @@ -219,12 +200,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe goToAddr(toolTwo, entryAddr); click2(); - // paste - plugin = getPlugin(toolTwo, ClipboardPlugin.class); - DockingActionIf pasteAction = getAction(plugin, "Paste"); - assertEnabled(pasteAction); - performAction(pasteAction, true); - waitForSwing(); + paste(toolTwo); CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class); cb.goToField(entryAddr, PlateFieldFactory.FIELD_NAME, 0, 0); @@ -283,12 +259,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe goToAddr(toolTwo, addr); click2(); - // paste - plugin = getPlugin(toolTwo, ClipboardPlugin.class); - DockingActionIf pasteAction = getAction(plugin, "Paste"); - assertEnabled(pasteAction); - performAction(pasteAction, true); - waitForSwing(); + paste(toolTwo); // verify the code browser field shows the comment func = programTwo.getListing().getFunctionAt(addr); @@ -338,12 +309,8 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe addr = getAddr(programTwo, 0x01004260); goToAddr(toolTwo, addr); click2(); - // paste - plugin = getPlugin(toolTwo, ClipboardPlugin.class); - DockingActionIf pasteAction = getAction(plugin, "Paste"); - assertEnabled(pasteAction); - performAction(pasteAction, true); - waitForSwing(); + + paste(toolTwo); // verify the code browser field shows the comment func = programTwo.getListing().getFunctionAt(addr); @@ -416,12 +383,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe goToAddr(toolTwo, 0x0100176f); click2(); - // paste - plugin = getPlugin(toolTwo, ClipboardPlugin.class); - DockingActionIf pasteAction = getAction(plugin, "Paste"); - assertEnabled(pasteAction); - performAction(pasteAction, true); - waitForSwing(); + paste(toolTwo); addr = getAddr(programTwo, 0x0100176f); // nothing should happen with the stack variable comments @@ -433,7 +395,36 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe assertEquals(1, f.getNumRows()); } - ///////////////////////////////////////////////////////////////////////// +//================================================================================================== +// Private Methods +//================================================================================================== + + private void paste(PluginTool tool) { + + ClipboardPlugin plugin = getPlugin(tool, ClipboardPlugin.class); + ClipboardContentProviderService service = + getCodeBrowserClipboardContentProviderService(plugin); + DockingActionIf pasteAction = getClipboardAction(plugin, service, "Paste"); + assertEnabled(pasteAction); + performAction(pasteAction, true); + waitForSwing(); + } + + private DockingActionIf getClipboardAction(ClipboardPlugin plugin, + ClipboardContentProviderService service, String actionName) { + + @SuppressWarnings("unchecked") + Map> map = + (Map>) getInstanceField( + "serviceActionMap", plugin); + List list = map.get(service); + for (DockingAction pluginAction : list) { + if (pluginAction.getName().equals(actionName)) { + return pluginAction; + } + } + return null; + } private void setupTool(PluginTool tool) throws Exception { tool.addPlugin(ClipboardPlugin.class.getName()); @@ -511,10 +502,9 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe ClipboardPlugin clipboardPlugin) { Map serviceMap = (Map) getInstanceField("serviceActionMap", clipboardPlugin); Set keySet = serviceMap.keySet(); - for (Object name : keySet) { - ClipboardContentProviderService service = (ClipboardContentProviderService) name; - if (service instanceof CodeBrowserClipboardProvider) { - return service; + for (Object service : keySet) { + if (service.getClass().equals(CodeBrowserClipboardProvider.class)) { + return (ClipboardContentProviderService) service; } } return null; 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 d14d14e97e..0c664cf7ff 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 @@ -43,6 +43,9 @@ public class ByteViewerClipboardProvider extends ByteCopier List copyTypesList = new LinkedList<>(); copyTypesList.add(BYTE_STRING_TYPE); copyTypesList.add(BYTE_STRING_NO_SPACE_TYPE); + copyTypesList.add(PYTHON_BYTE_STRING_TYPE); + copyTypesList.add(PYTHON_LIST_TYPE); + copyTypesList.add(CPP_BYTE_ARRAY_TYPE); return copyTypesList; } @@ -55,7 +58,6 @@ public class ByteViewerClipboardProvider extends ByteCopier PluginTool tool) { this.provider = provider; this.tool = tool; - currentProgram = provider.getProgram(); } @Override @@ -77,11 +79,6 @@ public class ByteViewerClipboardProvider extends ByteCopier @Override public boolean paste(Transferable pasteData) { - if (!supportsPasteTransferable(pasteData)) { - tool.setStatusInfo("Paste failed: No valid data on clipboard", true); - return false; - } - try { // try the default paste return pasteBytes(pasteData); @@ -103,30 +100,17 @@ public class ByteViewerClipboardProvider extends ByteCopier @Override public Transferable copy(TaskMonitor monitor) { String byteString = copyBytesAsString(currentSelection, true, monitor); - String textSelection = provider.getCurrentTextSelection(); - return new ByteViewerTransferable(byteString, textSelection); + String textSelection = getTextSelection(); + return new ByteStringTransferable(byteString, textSelection); + } + + protected String getTextSelection() { + return provider.getCurrentTextSelection(); } @Override public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor) { - - String byteString = null; - if (copyType == BYTE_STRING_TYPE) { - byteString = copyBytesAsString(currentSelection, true, monitor); - } - else if (copyType == BYTE_STRING_NO_SPACE_TYPE) { - byteString = copyBytesAsString(currentSelection, false, monitor); - } - else { - return null; - } - - return new ByteViewerTransferable(byteString); - } - - void setSelection(ProgramSelection selection) { - currentSelection = selection; - updateEnablement(); + return copyBytes(copyType, monitor); } private void updateEnablement() { @@ -138,6 +122,11 @@ public class ByteViewerClipboardProvider extends ByteCopier currentLocation = location; } + void setSelection(ProgramSelection selection) { + currentSelection = selection; + updateEnablement(); + } + void setProgram(Program p) { currentProgram = p; currentLocation = null; diff --git a/Ghidra/Features/ByteViewer/src/test/java/ghidra/app/plugin/core/byteviewer/ByteViewerClipboardProviderTest.java b/Ghidra/Features/ByteViewer/src/test/java/ghidra/app/plugin/core/byteviewer/ByteViewerClipboardProviderTest.java new file mode 100644 index 0000000000..6618d4dfb8 --- /dev/null +++ b/Ghidra/Features/ByteViewer/src/test/java/ghidra/app/plugin/core/byteviewer/ByteViewerClipboardProviderTest.java @@ -0,0 +1,239 @@ +/* ### + * 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.app.plugin.core.byteviewer; + +import static org.hamcrest.core.IsInstanceOf.*; +import static org.junit.Assert.*; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; + +import org.junit.Before; +import org.junit.Test; + +import docking.widgets.OptionDialog; +import ghidra.app.util.ByteCopier; +import ghidra.app.util.ByteCopier.ByteStringTransferable; +import ghidra.app.util.ByteCopier.ProgrammingByteStringTransferable; +import ghidra.app.util.ClipboardType; +import ghidra.framework.cmd.Command; +import ghidra.framework.model.DomainObject; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.DummyTool; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.AssertException; +import ghidra.util.task.TaskMonitor; + +public class ByteViewerClipboardProviderTest extends AbstractGhidraHeadedIntegrationTest { + + private Program program; + private ByteViewerClipboardProvider clipboardProvider; + + @Before + public void setUp() throws Exception { + + program = createProgram(); + PluginTool tool = new DummyTool() { + @Override + public boolean execute(Command command, DomainObject obj) { + boolean result = command.applyTo(obj); + if (!result) { + throw new AssertException("Failed to write bytes"); + } + return true; + } + }; + + clipboardProvider = new ByteViewerClipboardProvider(null, tool) { + + // overridden to stub this method, since we don't have a real provider + @Override + protected String getTextSelection() { + return null; + } + }; + clipboardProvider.setProgram(program); + } + + private Program createProgram() throws Exception { + ProgramBuilder builder = new ProgramBuilder("default", ProgramBuilder._TOY, this); + + builder.createMemory("test", "0x01001050", 20000); + + builder.setBytes("0x01001050", + "0e 5e f4 77 33 58 f4 77 91 45 f4 77 88 7c f4 77 8d 70 f5 77 05 62 f4 77 f0 a3 " + + "f4 77 09 56 f4 77 10 17 f4 77 f7 29 f6 77 02 59 f4 77"); + + builder.setBytes("0x01002050", "00 00 00 00 00 00 00 00 00 00 00 00 00"); + + builder.createMemoryReference("0x01002cc0", "0x01002cf0", RefType.DATA, + SourceType.USER_DEFINED); + builder.createMemoryReference("0x01002d04", "0x01002d0f", RefType.DATA, + SourceType.USER_DEFINED); + + DataType dt = DataType.DEFAULT; + Parameter p = new ParameterImpl(null, dt, builder.getProgram()); + builder.createEmptyFunction("ghidra", "0x01002cf5", 1, dt, p); + builder.createEmptyFunction("sscanf", "0x0100415a", 1, dt, p); + + builder.setBytes("0x0100418c", "ff 15 08 10 00 01"); + builder.disassemble("0x0100418c", 6); + + return builder.getProgram(); + } + + @Test + public void testCopyPasteSpecial_ByteString() throws Exception { + int length = 4; + clipboardProvider.setSelection(selection("0x01001050", length)); + ClipboardType type = ByteCopier.BYTE_STRING_TYPE; + Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY); + assertThat(transferable, instanceOf(ByteStringTransferable.class)); + + String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor); + assertEquals("0e 5e f4 77", byteString); + + String pasteAddress = "0x01002050"; + paste(pasteAddress, transferable); + assertBytesAt(pasteAddress, "0e 5e f4 77", length); + } + + @Test + public void testCopyPasteSpecial_ByteStringNoSpaces() throws Exception { + int length = 4; + clipboardProvider.setSelection(selection("0x01001050", length)); + ClipboardType type = ByteCopier.BYTE_STRING_NO_SPACE_TYPE; + Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY); + assertThat(transferable, instanceOf(ByteStringTransferable.class)); + + String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor); + assertEquals("0e5ef477", byteString); + + String pasteAddress = "0x01002050"; + paste(pasteAddress, transferable); + assertBytesAt(pasteAddress, "0e 5e f4 77", length); + } + + @Test + public void testCopyPasteSpecial_PythonByteString() throws Exception { + + int length = 4; + clipboardProvider.setSelection(selection("0x01001050", length)); + ClipboardType type = ByteCopier.PYTHON_BYTE_STRING_TYPE; + Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY); + assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class)); + + String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor); + assertEquals("b'\\x0e\\x5e\\xf4\\x77'", byteString); + + String pasteAddress = "0x01002050"; + paste(pasteAddress, transferable); + assertBytesAt(pasteAddress, "0e 5e f4 77", length); + } + + @Test + public void testCopyPasteSpecial_PythonListString() throws Exception { + + int length = 4; + clipboardProvider.setSelection(selection("0x01001050", 4)); + ClipboardType type = ByteCopier.PYTHON_LIST_TYPE; + Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY); + assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class)); + + String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor); + assertEquals("[ 0x0e, 0x5e, 0xf4, 0x77 ]", byteString); + + String pasteAddress = "0x01002050"; + paste(pasteAddress, transferable); + assertBytesAt(pasteAddress, "0e 5e f4 77", length); + } + + @Test + public void testCopyPasteSpecial_CppByteArray() throws Exception { + + int length = 4; + clipboardProvider.setSelection(selection("0x01001050", 4)); + ClipboardType type = ByteCopier.CPP_BYTE_ARRAY_TYPE; + Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY); + assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class)); + + String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor); + assertEquals("{ 0x0e, 0x5e, 0xf4, 0x77 }", byteString); + + String pasteAddress = "0x01002050"; + paste(pasteAddress, transferable); + assertBytesAt(pasteAddress, "0e 5e f4 77", length); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private void paste(String address, Transferable transferable) { + + tx(program, () -> { + doPaste(address, transferable); + }); + } + + private void doPaste(String address, Transferable transferable) { + clipboardProvider.setLocation(location(address)); + runSwing(() -> clipboardProvider.paste(transferable), false); + + OptionDialog confirmDialog = waitForDialogComponent(OptionDialog.class); + pressButtonByText(confirmDialog, "Yes"); + + waitForTasks(); + program.flushEvents(); + waitForSwing(); + } + + private void assertBytesAt(String address, String bytes, int length) + throws MemoryAccessException { + Memory memory = program.getMemory(); + byte[] memoryBytes = new byte[length]; + memory.getBytes(addr(address), memoryBytes, 0, length); + + String memoryByteString = NumericUtilities.convertBytesToString(memoryBytes, " "); + assertEquals(bytes, memoryByteString); + } + + private ProgramSelection selection(String addressString, int n) { + Address address = addr(addressString); + AddressSetView addresses = new AddressSet(address, address.add(n - 1)); + return new ProgramSelection(addresses); + } + + private Address addr(String addr) { + return program.getAddressFactory().getAddress(addr); + } + + private ProgramLocation location(String addressString) { + return new ProgramLocation(program, addr(addressString)); + } + +} diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ClipboardPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ClipboardPluginScreenShots.java index eb95b5b2f8..12cf132b93 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ClipboardPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ClipboardPluginScreenShots.java @@ -16,16 +16,22 @@ package help.screenshot; import java.awt.*; +import java.util.List; +import java.util.Map; +import java.util.Set; import javax.swing.*; import org.junit.Test; import docking.DialogComponentProvider; +import docking.action.DockingAction; +import docking.action.DockingActionIf; import docking.widgets.fieldpanel.FieldPanel; -import ghidra.app.plugin.core.clipboard.CopyPasteSpecialDialog; +import ghidra.app.plugin.core.clipboard.*; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; +import ghidra.app.services.ClipboardContentProviderService; import ghidra.app.util.viewer.field.MnemonicFieldFactory; import ghidra.program.util.ProgramSelection; import ghidra.util.Msg; @@ -33,10 +39,6 @@ import ghidra.util.exception.AssertException; public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator { - public ClipboardPluginScreenShots() { - super(); - } - @Test public void testCaptureCopySpecial() { @@ -47,9 +49,12 @@ public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator { makeSelection(0x401000, 0x401000); sel = cb.getCurrentSelection(); - Msg.debug(this, "selection: " + sel); - showCopySpecialDialog(); + CopyPasteSpecialDialog dialog = showCopySpecialDialog(); + Window window = SwingUtilities.windowForComponent(dialog.getComponent()); + + Dimension size = window.getSize(); + setWindowSize(window, size.width, 330); captureDialog(); } @@ -184,7 +189,39 @@ public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator { crop(imageBounds); } - private void showCopySpecialDialog() { - performAction("Copy Special", "ClipboardPlugin", false); + private CopyPasteSpecialDialog showCopySpecialDialog() { + ClipboardPlugin plugin = getPlugin(tool, ClipboardPlugin.class); + ClipboardContentProviderService service = getClipboardService(plugin); + DockingActionIf pasteAction = getLocalAction(service, "Copy Special", plugin); + performAction(pasteAction, false); + return waitForDialogComponent(CopyPasteSpecialDialog.class); + } + + private ClipboardContentProviderService getClipboardService( + ClipboardPlugin clipboardPlugin) { + Map serviceMap = (Map) getInstanceField("serviceActionMap", clipboardPlugin); + Set keySet = serviceMap.keySet(); + for (Object name : keySet) { + ClipboardContentProviderService service = (ClipboardContentProviderService) name; + if (service.getClass().equals(CodeBrowserClipboardProvider.class)) { + return service; + } + } + return null; + } + + @SuppressWarnings("unchecked") + private DockingAction getLocalAction(ClipboardContentProviderService service, String actionName, + ClipboardPlugin clipboardPlugin) { + Map actionsByService = + (Map) getInstanceField("serviceActionMap", clipboardPlugin); + List actionList = (List) actionsByService.get(service); + for (DockingAction pluginAction : actionList) { + if (pluginAction.getName().equals(actionName)) { + return pluginAction; + } + } + + return null; } } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteTestSuite.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteTestSuite.java new file mode 100644 index 0000000000..487a5cecd1 --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/CopyPasteTestSuite.java @@ -0,0 +1,36 @@ +/* ### + * 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.app.plugin.core.clipboard; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +import ghidra.app.plugin.core.byteviewer.ByteViewerClipboardProviderTest; + +//@formatter:off +@RunWith(Suite.class) +@SuiteClasses({ + ClipboardPluginTest.class, + ByteViewerClipboardProviderTest.class, + CodeBrowserClipboardProviderTest.class, + CopyPasteCommentsTest.class, + CopyPasteFunctionInfoTest.class +}) +public class CopyPasteTestSuite { + // in the annotation +} +//@formatter:on