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