mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-210 - Added new 'Copy Special' items to make copying from Ghidra to
Python and C++ easier
This commit is contained in:
parent
00ef824dbd
commit
ccedc6627a
15 changed files with 1024 additions and 416 deletions
|
@ -165,6 +165,15 @@
|
||||||
<LI><B>Byte String (No Spaces)</B> <A name="ByteString_NoSpaces"></A> - This is the same as
|
<LI><B>Byte String (No Spaces)</B> <A name="ByteString_NoSpaces"></A> - This is the same as
|
||||||
the Byte String format, except there are no delimiting spaces between bytes.</LI>
|
the Byte String format, except there are no delimiting spaces between bytes.</LI>
|
||||||
|
|
||||||
|
<LI><B>Python Byte String</B> - Copies the bytes into the Python byte string format, for
|
||||||
|
example: <CODE>b'\x41\xec\x49\x89'</CODE>.</LI>
|
||||||
|
|
||||||
|
<LI><B>Python List String</B> - Copies the bytes into the Python list format, for
|
||||||
|
example: <CODE>[ 0x66, 0x2e, 0x0f, 0x1f, 0x84 ]</CODE>.</LI>
|
||||||
|
|
||||||
|
<LI><B>C Array String</B> - Copies the bytes into the C array format, for
|
||||||
|
example: <CODE>{ 0x66, 0x2e, 0x0f, 0x1f, 0x84 }</CODE>.</LI>
|
||||||
|
|
||||||
<LI><B>Address</B> - Copies the address at the top of the selection.</LI>
|
<LI><B>Address</B> - Copies the address at the top of the selection.</LI>
|
||||||
</UL>
|
</UL>
|
||||||
|
|
||||||
|
@ -208,6 +217,17 @@
|
||||||
|
|
||||||
<LI><B>Byte String (No Spaces)</B> - This is the same as the Byte String format, except
|
<LI><B>Byte String (No Spaces)</B> - This is the same as the Byte String format, except
|
||||||
there are no delimiting spaces between bytes.</LI>
|
there are no delimiting spaces between bytes.</LI>
|
||||||
|
|
||||||
|
|
||||||
|
<LI><B>Python Byte String</B> - Copies the bytes into the Python byte string format, for
|
||||||
|
example: <CODE>b'\x41\xec\x49\x89'</CODE>.</LI>
|
||||||
|
|
||||||
|
<LI><B>Python List String</B> - Copies the bytes into the Python list format, for
|
||||||
|
example: <CODE>[ 0x66, 0x2e, 0x0f, 0x1f, 0x84 ]</CODE>.</LI>
|
||||||
|
|
||||||
|
<LI><B>C Array String</B> - Copies the bytes into the C array format, for
|
||||||
|
example: <CODE>{ 0x66, 0x2e, 0x0f, 0x1f, 0x84 }</CODE>.</LI>
|
||||||
|
|
||||||
</UL>
|
</UL>
|
||||||
|
|
||||||
<P>The Byte Viewer <B>Bytes In Memory</B> window can paste the following formats:</P>
|
<P>The Byte Viewer <B>Bytes In Memory</B> window can paste the following formats:</P>
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.8 KiB |
|
@ -377,7 +377,6 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl
|
||||||
if (!clipboardService.isValidContext(context)) {
|
if (!clipboardService.isValidContext(context)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return clipboardService.canCopy();
|
return clipboardService.canCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,7 +412,8 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl
|
||||||
}
|
}
|
||||||
|
|
||||||
Clipboard systemClipboard = getSystemClipboard();
|
Clipboard systemClipboard = getSystemClipboard();
|
||||||
return clipboardService.canPaste(getAvailableDataFlavors(systemClipboard));
|
DataFlavor[] flavors = getAvailableDataFlavors(systemClipboard);
|
||||||
|
return clipboardService.canPaste(flavors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -444,7 +444,6 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl
|
||||||
if (!clipboardService.isValidContext(context)) {
|
if (!clipboardService.isValidContext(context)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return clipboardService.canCopySpecial();
|
return clipboardService.canCopySpecial();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ import ghidra.program.model.symbol.*;
|
||||||
import ghidra.program.util.*;
|
import ghidra.program.util.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import util.CollectionUtils;
|
||||||
|
|
||||||
public class CodeBrowserClipboardProvider extends ByteCopier
|
public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
implements ClipboardContentProviderService {
|
implements ClipboardContentProviderService {
|
||||||
|
@ -85,20 +86,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final List<ClipboardType> PASTE_TYPES = createPasteTypesList();
|
|
||||||
|
|
||||||
private static List<ClipboardType> createPasteTypesList() {
|
|
||||||
List<ClipboardType> 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 boolean copyFromSelectionEnabled;
|
||||||
protected ComponentProvider componentProvider;
|
protected ComponentProvider componentProvider;
|
||||||
private ListingModel model;
|
private ListingModel model;
|
||||||
|
@ -157,10 +144,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
else if (element.equals(COMMENTS_TYPE.getFlavor())) {
|
else if (element.equals(COMMENTS_TYPE.getFlavor())) {
|
||||||
return pasteLabelsComments(pasteData, false, true);
|
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)) {
|
else if (element.equals(LabelStringTransferable.labelStringFlavor)) {
|
||||||
return pasteLabelString(pasteData);
|
return pasteLabelString(pasteData);
|
||||||
}
|
}
|
||||||
|
@ -169,10 +152,8 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// last ditch effort, try to paste as a byte string
|
if (super.pasteBytes(pasteData)) {
|
||||||
String string = (String) pasteData.getTransferData(DataFlavor.stringFlavor);
|
return true;
|
||||||
if (string != null) {
|
|
||||||
return pasteByteString(string);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tool.setStatusInfo("Paste failed: unsupported data type", true);
|
tool.setStatusInfo("Paste failed: unsupported data type", true);
|
||||||
|
@ -181,36 +162,14 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
String msg = e.getMessage();
|
String msg = e.getMessage();
|
||||||
if (msg == null) {
|
if (msg == null) {
|
||||||
msg = e.toString();
|
msg = e.toString();
|
||||||
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Msg.error(this, "Unexpected Exception: " + msg, e);
|
||||||
tool.setStatusInfo("Paste failed: " + msg, true);
|
tool.setStatusInfo("Paste failed: " + msg, true);
|
||||||
}
|
}
|
||||||
return false;
|
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
|
@Override
|
||||||
public List<ClipboardType> getCurrentCopyTypes() {
|
public List<ClipboardType> getCurrentCopyTypes() {
|
||||||
return COPY_TYPES;
|
return COPY_TYPES;
|
||||||
|
@ -234,45 +193,8 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
else if (copyType == COMMENTS_TYPE) {
|
else if (copyType == COMMENTS_TYPE) {
|
||||||
return copyLabelsComments(false, true);
|
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);
|
|
||||||
}
|
|
||||||
else if (copyType == PYTHON_BYTE_STRING_TYPE) {
|
|
||||||
String byteString = "b'"
|
|
||||||
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", "\\x") + "'";
|
|
||||||
return new ByteViewerTransferable(byteString);
|
|
||||||
}
|
|
||||||
else if (copyType == PYTHON_LIST_TYPE) {
|
|
||||||
String byteString = "[ "
|
|
||||||
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", ", 0x") + " ]";
|
|
||||||
return new ByteViewerTransferable(byteString);
|
|
||||||
}
|
|
||||||
else if (copyType == CPP_BYTE_ARRAY_TYPE) {
|
|
||||||
String byteString = "{ "
|
|
||||||
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", ", 0x") + " }";
|
|
||||||
return new ByteViewerTransferable(byteString);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return copyBytes(copyType, monitor);
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStringContent(String text) {
|
public void setStringContent(String text) {
|
||||||
|
@ -287,6 +209,12 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
currentLocation = location;
|
currentLocation = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSelection(ProgramSelection selection) {
|
||||||
|
currentSelection = selection;
|
||||||
|
copyFromSelectionEnabled = selection != null && !selection.isEmpty();
|
||||||
|
notifyStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
public void setProgram(Program p) {
|
public void setProgram(Program p) {
|
||||||
currentProgram = p;
|
currentProgram = p;
|
||||||
currentLocation = null;
|
currentLocation = null;
|
||||||
|
@ -323,11 +251,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
}
|
}
|
||||||
else if (currentLocation instanceof BytesFieldLocation) {
|
else if (currentLocation instanceof BytesFieldLocation) {
|
||||||
// bytes are special--let them get copied and pasted as normal
|
// 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);
|
return copyByteString(address);
|
||||||
}
|
}
|
||||||
else if (currentLocation instanceof OperandFieldLocation) {
|
else if (currentLocation instanceof OperandFieldLocation) {
|
||||||
|
@ -439,7 +362,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
|
|
||||||
private Transferable copyByteString(Address address) {
|
private Transferable copyByteString(Address address) {
|
||||||
AddressSet set = new AddressSet(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) {
|
private CodeUnitInfoTransferable copyLabelsComments(boolean copyLabels, boolean copyComments) {
|
||||||
|
@ -451,13 +374,13 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
return new CodeUnitInfoTransferable(list);
|
return new CodeUnitInfoTransferable(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") // assumed correct data; handled in exception case
|
|
||||||
private boolean pasteLabelsComments(Transferable pasteData, boolean pasteLabels,
|
private boolean pasteLabelsComments(Transferable pasteData, boolean pasteLabels,
|
||||||
boolean pasteComments) {
|
boolean pasteComments) {
|
||||||
try {
|
try {
|
||||||
List<CodeUnitInfo> list = (List<CodeUnitInfo>) pasteData.getTransferData(
|
List<?> list = (List<?>) pasteData.getTransferData(
|
||||||
CodeUnitInfoTransferable.localDataTypeFlavor);
|
CodeUnitInfoTransferable.localDataTypeFlavor);
|
||||||
Command cmd = new CodeUnitInfoPasteCmd(currentLocation.getAddress(), list, pasteLabels,
|
List<CodeUnitInfo> infos = CollectionUtils.asList(list, CodeUnitInfo.class);
|
||||||
|
Command cmd = new CodeUnitInfoPasteCmd(currentLocation.getAddress(), infos, pasteLabels,
|
||||||
pasteComments);
|
pasteComments);
|
||||||
return tool.execute(cmd, currentProgram);
|
return tool.execute(cmd, currentProgram);
|
||||||
}
|
}
|
||||||
|
@ -494,36 +417,43 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
return tool.execute(cmd, currentProgram);
|
return tool.execute(cmd, currentProgram);
|
||||||
}
|
}
|
||||||
else if (currentLocation instanceof OperandFieldLocation) {
|
else if (currentLocation instanceof OperandFieldLocation) {
|
||||||
OperandFieldLocation operandLocation = (OperandFieldLocation) currentLocation;
|
return pasteOperandField((OperandFieldLocation) currentLocation, labelName);
|
||||||
int opIndex = operandLocation.getOperandIndex();
|
}
|
||||||
Listing listing = currentProgram.getListing();
|
|
||||||
Instruction instruction = listing.getInstructionAt(operandLocation.getAddress());
|
|
||||||
if (instruction == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference reference = instruction.getPrimaryReference(opIndex);
|
// try pasting onto something that is not a label
|
||||||
if (reference == null) {
|
return maybePasteNonLabelString(labelName);
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Variable var = currentProgram.getReferenceManager().getReferencedVariable(reference);
|
private boolean pasteOperandField(OperandFieldLocation operandLocation, String labelName) {
|
||||||
if (var != null) {
|
|
||||||
SetVariableNameCmd cmd =
|
|
||||||
new SetVariableNameCmd(var, labelName, SourceType.USER_DEFINED);
|
|
||||||
return tool.execute(cmd, currentProgram);
|
|
||||||
}
|
|
||||||
|
|
||||||
SymbolTable symbolTable = currentProgram.getSymbolTable();
|
int opIndex = operandLocation.getOperandIndex();
|
||||||
Symbol symbol = symbolTable.getSymbol(reference);
|
Listing listing = currentProgram.getListing();
|
||||||
if ((symbol instanceof CodeSymbol) || (symbol instanceof FunctionSymbol)) {
|
Instruction instruction = listing.getInstructionAt(operandLocation.getAddress());
|
||||||
String oldName = symbol.getName();
|
if (instruction == null) {
|
||||||
Namespace namespace = symbol.getParentNamespace();
|
return false;
|
||||||
Address symbolAddress = symbol.getAddress();
|
}
|
||||||
RenameLabelCmd cmd = new RenameLabelCmd(symbolAddress, oldName, labelName,
|
|
||||||
namespace, SourceType.USER_DEFINED);
|
Reference reference = instruction.getPrimaryReference(opIndex);
|
||||||
return tool.execute(cmd, currentProgram);
|
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
|
// try pasting onto something that is not a label
|
||||||
|
@ -707,8 +637,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (flavor.equals(DataFlavor.stringFlavor)) {
|
if (flavor.equals(DataFlavor.stringFlavor)) {
|
||||||
// TODO: check if it is a valid hex string...
|
return true; // check if it is a valid hex string?
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.services;
|
package ghidra.app.services;
|
||||||
|
|
||||||
import ghidra.app.util.ClipboardType;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
import java.awt.datatransfer.DataFlavor;
|
import java.awt.datatransfer.DataFlavor;
|
||||||
import java.awt.datatransfer.Transferable;
|
import java.awt.datatransfer.Transferable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -26,21 +23,26 @@ import javax.swing.event.ChangeListener;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.ComponentProvider;
|
import docking.ComponentProvider;
|
||||||
|
import ghidra.app.util.ClipboardType;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <code>ClipboardContentProvider</code> determines what types of
|
* Determines what types of transfer data can be placed on the clipboard, as well as if
|
||||||
* transfer data can be placed on the clipboard, and cut, copy, and paste.
|
* cut, copy, and paste operations are supported
|
||||||
*/
|
*/
|
||||||
public interface ClipboardContentProviderService {
|
public interface ClipboardContentProviderService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the component provider associated with this
|
* Returns the component provider associated with this service
|
||||||
* ClipboardContentProviderService.
|
* @return the provider
|
||||||
*/
|
*/
|
||||||
public ComponentProvider getComponentProvider();
|
public ComponentProvider getComponentProvider();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers the default copy operation
|
* 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);
|
public Transferable copy(TaskMonitor monitor);
|
||||||
|
|
||||||
|
@ -49,44 +51,49 @@ public interface ClipboardContentProviderService {
|
||||||
* @param copyType contains the data flavor of the clipboard contents
|
* @param copyType contains the data flavor of the clipboard contents
|
||||||
* @param monitor monitor that shows progress of the copy to clipboard, and
|
* @param monitor monitor that shows progress of the copy to clipboard, and
|
||||||
* may be canceled
|
* may be canceled
|
||||||
|
* @return the created transferable; null if the copy was unsuccessful
|
||||||
*/
|
*/
|
||||||
public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor);
|
public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers the default paste operation for the given transferable
|
* 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);
|
public boolean paste(Transferable pasteData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the currently active ClipboardTypes for copying with the current context
|
* Gets the currently active ClipboardTypes for copying with the current context
|
||||||
|
* @return the types
|
||||||
*/
|
*/
|
||||||
public List<ClipboardType> getCurrentCopyTypes();
|
public List<ClipboardType> getCurrentCopyTypes();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether the given context is valid for actions on popup menus.
|
* Return whether the given context is valid for actions on popup menus.
|
||||||
* @param context the context of where the popup menu will be positioned.
|
* @param context the context of where the popup menu will be positioned.
|
||||||
|
* @return true if valid
|
||||||
*/
|
*/
|
||||||
public boolean isValidContext(ActionContext context);
|
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
|
* 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
|
* be used in conjunction with {@link #copy(TaskMonitor)} in order to add menu items to
|
||||||
* popup menus but to have them enabled when appropriate.
|
* popup menus but to have them enabled when appropriate.
|
||||||
|
* @return true if copy should be enabled
|
||||||
*/
|
*/
|
||||||
public boolean enableCopy();
|
public boolean enableCopy();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if copySpecial actions should be enabled;
|
* Returns true if copySpecial actions should be enabled;
|
||||||
* @return
|
* @return true if copySpecial actions should be enabled;
|
||||||
*/
|
*/
|
||||||
public boolean enableCopySpecial();
|
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
|
* 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
|
* be used in conjunction with {@link #paste(Transferable)} in order to add menu items to
|
||||||
* popup menus but to have them enabled when appropriate.
|
* popup menus but to have them enabled when appropriate.
|
||||||
|
* @return true if paste should be enabled
|
||||||
*/
|
*/
|
||||||
public boolean enablePaste();
|
public boolean enablePaste();
|
||||||
|
|
||||||
|
@ -130,6 +137,7 @@ public interface ClipboardContentProviderService {
|
||||||
/**
|
/**
|
||||||
* Returns true if the given service provider can currently perform a 'copy special'
|
* Returns true if the given service provider can currently perform a 'copy special'
|
||||||
* operation.
|
* operation.
|
||||||
|
* @return true if copy special is enabled
|
||||||
*/
|
*/
|
||||||
public boolean canCopySpecial();
|
public boolean canCopySpecial();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ package ghidra.app.util;
|
||||||
import java.awt.datatransfer.*;
|
import java.awt.datatransfer.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import docking.dnd.GenericDataFlavor;
|
import docking.dnd.GenericDataFlavor;
|
||||||
import docking.dnd.StringTransferable;
|
import docking.dnd.StringTransferable;
|
||||||
|
@ -25,8 +27,7 @@ import docking.widgets.OptionDialog;
|
||||||
import ghidra.framework.cmd.Command;
|
import ghidra.framework.cmd.Command;
|
||||||
import ghidra.framework.model.DomainObject;
|
import ghidra.framework.model.DomainObject;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.address.AddressSetView;
|
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.mem.Memory;
|
import ghidra.program.model.mem.Memory;
|
||||||
import ghidra.program.model.mem.MemoryAccessException;
|
import ghidra.program.model.mem.MemoryAccessException;
|
||||||
|
@ -64,6 +65,19 @@ public abstract class ByteCopier {
|
||||||
public static final ClipboardType CPP_BYTE_ARRAY_TYPE =
|
public static final ClipboardType CPP_BYTE_ARRAY_TYPE =
|
||||||
new ClipboardType(CPP_BYTE_ARRAY_FLAVOR, "C Array");
|
new ClipboardType(CPP_BYTE_ARRAY_FLAVOR, "C Array");
|
||||||
|
|
||||||
|
private static final Map<DataFlavor, Pattern> 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() {
|
private static DataFlavor createByteStringLocalDataTypeFlavor() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -72,7 +86,7 @@ public abstract class ByteCopier {
|
||||||
"Local flavor--byte string with spaces");
|
"Local flavor--byte string with spaces");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
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);
|
"Unexpected exception creating data flavor for byte string", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +101,7 @@ public abstract class ByteCopier {
|
||||||
"Local flavor--byte string with NO spaces");
|
"Local flavor--byte string with NO spaces");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
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);
|
"Unexpected exception creating data flavor for byte string with no spaces", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +116,7 @@ public abstract class ByteCopier {
|
||||||
"Local flavor--Python byte string");
|
"Local flavor--Python byte string");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Msg.showError(ByteCopier.class, null, "Could Not Create Data Flavor",
|
Msg.error(ByteCopier.class,
|
||||||
"Unexpected exception creating data flavor for Python byte string", e);
|
"Unexpected exception creating data flavor for Python byte string", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +131,7 @@ public abstract class ByteCopier {
|
||||||
"Local flavor--Python list");
|
"Local flavor--Python list");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Msg.showError(ByteCopier.class, null, "Could Not Create Data Flavor",
|
Msg.error(ByteCopier.class,
|
||||||
"Unexpected exception creating data flavor for Python list", e);
|
"Unexpected exception creating data flavor for Python list", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +146,7 @@ public abstract class ByteCopier {
|
||||||
"Local flavor--C++ array");
|
"Local flavor--C++ array");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Msg.showError(ByteCopier.class, null, "Could Not Create Data Flavor",
|
Msg.error(ByteCopier.class,
|
||||||
"Unexpected exception creating data flavor for C array", e);
|
"Unexpected exception creating data flavor for C array", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,8 +162,12 @@ public abstract class ByteCopier {
|
||||||
// limit construction
|
// limit construction
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Transferable copyBytes(boolean includeSpaces, TaskMonitor monitor) {
|
protected AddressSetView getSelectedAddresses() {
|
||||||
return copyBytes(currentSelection, includeSpaces, monitor);
|
AddressSetView addressSet = currentSelection;
|
||||||
|
if (addressSet == null || addressSet.isEmpty()) {
|
||||||
|
return new AddressSet(currentLocation.getAddress());
|
||||||
|
}
|
||||||
|
return currentSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Transferable copyBytes(AddressSetView addresses, boolean includeSpaces,
|
protected Transferable copyBytes(AddressSetView addresses, boolean includeSpaces,
|
||||||
|
@ -160,42 +178,18 @@ public abstract class ByteCopier {
|
||||||
protected String copyBytesAsString(AddressSetView addresses, boolean includeSpaces,
|
protected String copyBytesAsString(AddressSetView addresses, boolean includeSpaces,
|
||||||
TaskMonitor monitor) {
|
TaskMonitor monitor) {
|
||||||
|
|
||||||
Memory memory = currentProgram.getMemory();
|
|
||||||
String delimiter = includeSpaces ? " " : "";
|
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);
|
ByteIterator bytes = new ByteIterator(addresses, memory);
|
||||||
return NumericUtilities.convertBytesToString(bytes, delimiter);
|
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) {
|
private byte[] getBytes(String transferString) {
|
||||||
|
|
||||||
byte[] bytes = getHexBytes(transferString);
|
byte[] bytes = getHexBytes(transferString);
|
||||||
|
@ -271,27 +265,121 @@ public abstract class ByteCopier {
|
||||||
return null;
|
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)
|
protected boolean pasteBytes(Transferable pasteData)
|
||||||
throws UnsupportedFlavorException, IOException {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pasteData.isDataFlavorSupported(BYTE_STRING_FLAVOR)) {
|
// see if the pasted data is similar to other known programming formats
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
String string = (String) pasteData.getTransferData(DataFlavor.stringFlavor);
|
String string = (String) pasteData.getTransferData(DataFlavor.stringFlavor);
|
||||||
|
if (string == null) {
|
||||||
|
tool.setStatusInfo("Paste failed: no string data", true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return pasteByteString(string);
|
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) {
|
protected boolean pasteByteString(final String string) {
|
||||||
Command cmd = new Command() {
|
Command cmd = new Command() {
|
||||||
|
|
||||||
|
@ -299,75 +387,100 @@ public abstract class ByteCopier {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean applyTo(DomainObject domainObject) {
|
public boolean applyTo(DomainObject domainObject) {
|
||||||
if (domainObject instanceof Program) {
|
if (!(domainObject instanceof Program)) {
|
||||||
String validString = string;
|
return false;
|
||||||
if (!isOnlyAsciiBytes(string)) {
|
}
|
||||||
tool.setStatusInfo("Pasted string contained non-text ascii bytes. " +
|
|
||||||
"Only the ascii text was pasted.", true);
|
|
||||||
|
|
||||||
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);
|
private boolean confirmPaste(String validString) {
|
||||||
if (bytes == null) {
|
|
||||||
status = "Improper data format (expected sequence of hex bytes)";
|
// 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();
|
tool.beep();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
CodeUnit codeUnit = listing.getCodeUnitContaining(address);
|
||||||
// Ensure that we are not writing over instructions
|
if (!(codeUnit instanceof Data) || ((Data) codeUnit).isDefined()) {
|
||||||
Program curProgram = (Program) domainObject;
|
status = "Cannot paste on top of defined instructions/data";
|
||||||
Listing listing = curProgram.getListing();
|
tool.beep();
|
||||||
Address curAddr = currentLocation.getAddress();
|
return false;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
int length = codeUnit.getLength();
|
||||||
// Per SCR 11212, ask the user before pasting a string into the program.
|
i += length;
|
||||||
// Since having a string in the clipboard is so common, this is to prevent
|
address = codeUnit.getMaxAddress().next();
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -445,33 +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(),
|
private List<DataFlavor> flavorList;
|
||||||
BYTE_STRING_TYPE.getFlavor(), PYTHON_BYTE_STRING_TYPE.getFlavor(),
|
private DataFlavor[] flavors;
|
||||||
PYTHON_LIST_TYPE.getFlavor(), CPP_BYTE_ARRAY_TYPE.getFlavor(),
|
private DataFlavor programmingFlavor;
|
||||||
DataFlavor.stringFlavor };
|
private String byteString;
|
||||||
private final List<DataFlavor> flavorList = Arrays.asList(flavors);
|
|
||||||
|
|
||||||
private final String byteString;
|
public ProgrammingByteStringTransferable(String byteString, DataFlavor flavor) {
|
||||||
|
|
||||||
private final String byteViewerRepresentation;
|
|
||||||
|
|
||||||
public ByteViewerTransferable(String byteString) {
|
|
||||||
this(byteString, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ByteViewerTransferable(String byteString, String byteViewerRepresentation) {
|
|
||||||
this.byteString = byteString;
|
this.byteString = byteString;
|
||||||
this.byteViewerRepresentation = byteViewerRepresentation;
|
this.programmingFlavor = flavor;
|
||||||
|
this.flavors = new DataFlavor[] { flavor, DataFlavor.stringFlavor };
|
||||||
|
this.flavorList = Arrays.asList(flavors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getTransferData(DataFlavor flavor)
|
public Object getTransferData(DataFlavor flavor)
|
||||||
throws UnsupportedFlavorException, IOException {
|
throws UnsupportedFlavorException, IOException {
|
||||||
if (flavor.equals(DataFlavor.stringFlavor)) {
|
if (flavor.equals(DataFlavor.stringFlavor)) {
|
||||||
if (byteViewerRepresentation != null) {
|
return byteString; // just default to the byte string when no 'special' string data
|
||||||
return byteViewerRepresentation;
|
}
|
||||||
|
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<DataFlavor> 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
|
return byteString; // just default to the byte string when no 'special' string data
|
||||||
}
|
}
|
||||||
|
@ -481,15 +630,6 @@ public abstract class ByteCopier {
|
||||||
if (flavor.equals(BYTE_STRING_NO_SPACE_TYPE.getFlavor())) {
|
if (flavor.equals(BYTE_STRING_NO_SPACE_TYPE.getFlavor())) {
|
||||||
return byteString;
|
return byteString;
|
||||||
}
|
}
|
||||||
if (flavor.equals(PYTHON_BYTE_STRING_TYPE.getFlavor())) {
|
|
||||||
return byteString;
|
|
||||||
}
|
|
||||||
if (flavor.equals(PYTHON_LIST_TYPE.getFlavor())) {
|
|
||||||
return byteString;
|
|
||||||
}
|
|
||||||
if (flavor.equals(CPP_BYTE_ARRAY_TYPE.getFlavor())) {
|
|
||||||
return byteString;
|
|
||||||
}
|
|
||||||
throw new UnsupportedFlavorException(flavor);
|
throw new UnsupportedFlavorException(flavor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -37,24 +36,23 @@ public class ClipboardType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the DataFlavor for this ClipboardType
|
* Returns the DataFlavor for this type
|
||||||
|
* @return the flavor
|
||||||
*/
|
*/
|
||||||
public DataFlavor getFlavor() {
|
public DataFlavor getFlavor() {
|
||||||
return flavor;
|
return flavor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of this Clipboard Type.
|
* Returns the name of this type
|
||||||
|
* @return the name
|
||||||
*/
|
*/
|
||||||
public String getTypeName() {
|
public String getTypeName() {
|
||||||
return typeName;
|
return typeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see java.lang.Object#toString()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return typeName;
|
return typeName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -132,6 +132,7 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
|
||||||
pmTwo.openProgram(df2);
|
pmTwo.openProgram(df2);
|
||||||
programTwo = (ProgramDB) pmTwo.getCurrentProgram();
|
programTwo = (ProgramDB) pmTwo.getCurrentProgram();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -145,7 +146,8 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
|
||||||
goTo(toolOne, 0x32a);
|
goTo(toolOne, 0x32a);
|
||||||
|
|
||||||
ClipboardPlugin plugin = getPlugin(toolOne, ClipboardPlugin.class);
|
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());
|
assertEnabled(pasteAction, cb.getProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,8 +196,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
|
||||||
// in Program One, add MyLabel at 032a
|
// in Program One, add MyLabel at 032a
|
||||||
|
|
||||||
int transactionID = programOne.startTransaction("test");
|
int transactionID = programOne.startTransaction("test");
|
||||||
programOne.getSymbolTable().createLabel(addr(programOne, 0x032a), "MyLabel",
|
programOne.getSymbolTable()
|
||||||
SourceType.USER_DEFINED);
|
.createLabel(addr(programOne, 0x032a), "MyLabel",
|
||||||
|
SourceType.USER_DEFINED);
|
||||||
programOne.endTransaction(transactionID, true);
|
programOne.endTransaction(transactionID, true);
|
||||||
|
|
||||||
goTo(toolTwo, 0x0326);
|
goTo(toolTwo, 0x0326);
|
||||||
|
@ -385,8 +388,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
|
||||||
programOne.getSymbolTable().getSymbol("LAB_0331", addr(programOne, 0x0331), null);
|
programOne.getSymbolTable().getSymbol("LAB_0331", addr(programOne, 0x0331), null);
|
||||||
// in Browser(1) change default label at 331 to JUNK
|
// in Browser(1) change default label at 331 to JUNK
|
||||||
int transactionID = programOne.startTransaction("test");
|
int transactionID = programOne.startTransaction("test");
|
||||||
programOne.getSymbolTable().createLabel(addr(programOne, 0x0331), "JUNK",
|
programOne.getSymbolTable()
|
||||||
SourceType.USER_DEFINED);
|
.createLabel(addr(programOne, 0x0331), "JUNK",
|
||||||
|
SourceType.USER_DEFINED);
|
||||||
programOne.endTransaction(transactionID, true);
|
programOne.endTransaction(transactionID, true);
|
||||||
//
|
//
|
||||||
// in Browser(1) go to 331
|
// in Browser(1) go to 331
|
||||||
|
@ -426,8 +430,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
|
||||||
public void testPasteAtMultipleLabels() throws Exception {
|
public void testPasteAtMultipleLabels() throws Exception {
|
||||||
// in program 2, create a second label, JUNK2, at 0331
|
// in program 2, create a second label, JUNK2, at 0331
|
||||||
int transactionID = programOne.startTransaction("test");
|
int transactionID = programOne.startTransaction("test");
|
||||||
programOne.getSymbolTable().createLabel(addr(programOne, 0x331), "JUNK2",
|
programOne.getSymbolTable()
|
||||||
SourceType.USER_DEFINED);
|
.createLabel(addr(programOne, 0x331), "JUNK2",
|
||||||
|
SourceType.USER_DEFINED);
|
||||||
programOne.endTransaction(transactionID, true);
|
programOne.endTransaction(transactionID, true);
|
||||||
|
|
||||||
// in Browser(2) select 331 through 334, contains "RSR10"
|
// in Browser(2) select 331 through 334, contains "RSR10"
|
||||||
|
@ -474,8 +479,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
|
||||||
@Test
|
@Test
|
||||||
public void testPasteWhereUserLabelExists() throws Exception {
|
public void testPasteWhereUserLabelExists() throws Exception {
|
||||||
int transactionID = programOne.startTransaction("test");
|
int transactionID = programOne.startTransaction("test");
|
||||||
programOne.getSymbolTable().createLabel(addr(programOne, 0x331), "JUNK2",
|
programOne.getSymbolTable()
|
||||||
SourceType.USER_DEFINED);
|
.createLabel(addr(programOne, 0x331), "JUNK2",
|
||||||
|
SourceType.USER_DEFINED);
|
||||||
programOne.endTransaction(transactionID, true);
|
programOne.endTransaction(transactionID, true);
|
||||||
|
|
||||||
// in Browser(2) select 331 through 334, contains "RSR10"
|
// 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);
|
cb.goToField(addr(programOne, 0x0331), LabelFieldFactory.FIELD_NAME, 0, 0);
|
||||||
f = (ListingTextField) cb.getCurrentField();
|
f = (ListingTextField) cb.getCurrentField();
|
||||||
assertEquals(programOne.getSymbolTable().getSymbol("LAB_00000331", addr(programOne, 0x0331),
|
assertEquals(programOne.getSymbolTable()
|
||||||
null).getName(), f.getText());
|
.getSymbol("LAB_00000331", addr(programOne, 0x0331),
|
||||||
|
null)
|
||||||
|
.getName(),
|
||||||
|
f.getText());
|
||||||
|
|
||||||
cb.goToField(addr(programOne, 0x031b), LabelFieldFactory.FIELD_NAME, 0, 0);
|
cb.goToField(addr(programOne, 0x031b), LabelFieldFactory.FIELD_NAME, 0, 0);
|
||||||
f = (ListingTextField) cb.getCurrentField();
|
f = (ListingTextField) cb.getCurrentField();
|
||||||
assertEquals(programOne.getSymbolTable().getSymbol("LAB_0000031b", addr(programOne, 0x031b),
|
assertEquals(programOne.getSymbolTable()
|
||||||
null).getName(), f.getText());
|
.getSymbol("LAB_0000031b", addr(programOne, 0x031b),
|
||||||
|
null)
|
||||||
|
.getName(),
|
||||||
|
f.getText());
|
||||||
|
|
||||||
redo(programOne);
|
redo(programOne);
|
||||||
|
|
||||||
|
@ -616,8 +628,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
|
||||||
// create a function over the range 0x31b through 0x0343.
|
// create a function over the range 0x31b through 0x0343.
|
||||||
int transactionID = programOne.startTransaction("test");
|
int transactionID = programOne.startTransaction("test");
|
||||||
String name = SymbolUtilities.getDefaultFunctionName(min);
|
String name = SymbolUtilities.getDefaultFunctionName(min);
|
||||||
programOne.getListing().createFunction(name, min, new AddressSet(min, max),
|
programOne.getListing()
|
||||||
SourceType.USER_DEFINED);
|
.createFunction(name, min, new AddressSet(min, max),
|
||||||
|
SourceType.USER_DEFINED);
|
||||||
programOne.endTransaction(transactionID, true);
|
programOne.endTransaction(transactionID, true);
|
||||||
programOne.flushEvents();
|
programOne.flushEvents();
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
@ -710,19 +723,23 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
|
||||||
private void copyToolTwoLabels() {
|
private void copyToolTwoLabels() {
|
||||||
ClipboardPlugin plugin = getPlugin(toolTwo, ClipboardPlugin.class);
|
ClipboardPlugin plugin = getPlugin(toolTwo, ClipboardPlugin.class);
|
||||||
ClipboardContentProviderService service =
|
ClipboardContentProviderService service =
|
||||||
getCodeBrowserClipboardContentProviderService(plugin);
|
getClipboardService(plugin);
|
||||||
DockingAction action = getLocalAction(service, "Copy Special", plugin);
|
DockingAction action = getLocalAction(service, "Copy Special", plugin);
|
||||||
assertNotNull(action);
|
assertNotNull(action);
|
||||||
assertEnabled(action, cb2.getProvider());
|
assertEnabled(action, cb2.getProvider());
|
||||||
|
|
||||||
plugin.copySpecial(service, CodeBrowserClipboardProvider.LABELS_COMMENTS_TYPE);
|
runSwing(
|
||||||
|
() -> plugin.copySpecial(service, CodeBrowserClipboardProvider.LABELS_COMMENTS_TYPE));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pasteToolOne() {
|
private void pasteToolOne() {
|
||||||
|
|
||||||
ClipboardPlugin plugin = getPlugin(toolOne, ClipboardPlugin.class);
|
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());
|
assertEnabled(pasteAction, cb.getProvider());
|
||||||
performAction(pasteAction, true);
|
performAction(pasteAction, true);
|
||||||
|
waitForSwing();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupTool(PluginTool tool) throws Exception {
|
private void setupTool(PluginTool tool) throws Exception {
|
||||||
|
@ -809,13 +826,13 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClipboardContentProviderService getCodeBrowserClipboardContentProviderService(
|
private ClipboardContentProviderService getClipboardService(
|
||||||
ClipboardPlugin clipboardPlugin) {
|
ClipboardPlugin clipboardPlugin) {
|
||||||
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
|
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
|
||||||
Set<?> keySet = serviceMap.keySet();
|
Set<?> keySet = serviceMap.keySet();
|
||||||
for (Object name : keySet) {
|
for (Object name : keySet) {
|
||||||
ClipboardContentProviderService service = (ClipboardContentProviderService) name;
|
ClipboardContentProviderService service = (ClipboardContentProviderService) name;
|
||||||
if (service instanceof CodeBrowserClipboardProvider) {
|
if (service.getClass().equals(CodeBrowserClipboardProvider.class)) {
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -825,17 +842,12 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private DockingAction getLocalAction(ClipboardContentProviderService service, String actionName,
|
private DockingAction getLocalAction(ClipboardContentProviderService service, String actionName,
|
||||||
ClipboardPlugin clipboardPlugin) {
|
ClipboardPlugin clipboardPlugin) {
|
||||||
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
|
Map<?, ?> actionsByService =
|
||||||
Set<?> keySet = serviceMap.keySet();
|
(Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
|
||||||
for (Object name : keySet) {
|
List<DockingAction> actionList = (List<DockingAction>) actionsByService.get(service);
|
||||||
ClipboardContentProviderService currentService = (ClipboardContentProviderService) name;
|
for (DockingAction pluginAction : actionList) {
|
||||||
if (currentService == service) {
|
if (pluginAction.getName().equals(actionName)) {
|
||||||
List<DockingAction> actionList = (List<DockingAction>) serviceMap.get(service);
|
return pluginAction;
|
||||||
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) {
|
private void assertEnabled(DockingActionIf action, ComponentProvider provider) {
|
||||||
boolean isEnabled =
|
boolean isEnabled =
|
||||||
runSwing(() -> action.isEnabledForContext(provider.getActionContext(null)));
|
runSwing(() -> {
|
||||||
assertTrue(isEnabled);
|
return action.isEnabledForContext(provider.getActionContext(null));
|
||||||
|
});
|
||||||
|
assertTrue("Action was not enabled when it should be", isEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.clipboard;
|
package ghidra.app.plugin.core.clipboard;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
|
@ -26,6 +25,7 @@ import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
|
import docking.action.DockingAction;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.widgets.fieldpanel.FieldPanel;
|
import docking.widgets.fieldpanel.FieldPanel;
|
||||||
import ghidra.app.cmd.function.SetVariableCommentCmd;
|
import ghidra.app.cmd.function.SetVariableCommentCmd;
|
||||||
|
@ -53,11 +53,8 @@ import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.test.*;
|
import ghidra.test.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test copy/paste function information.
|
* Test copy/paste function information
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTest {
|
public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
private TestEnv env;
|
private TestEnv env;
|
||||||
|
@ -72,14 +69,6 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
private Options fieldOptions2;
|
private Options fieldOptions2;
|
||||||
private CodeBrowserPlugin cb1;
|
private CodeBrowserPlugin cb1;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for CopyPasteFunctionInfoTest.
|
|
||||||
* @param arg0
|
|
||||||
*/
|
|
||||||
public CopyPasteFunctionInfoTest() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Program buildNotepad(String name) throws Exception {
|
private Program buildNotepad(String name) throws Exception {
|
||||||
ToyProgramBuilder builder = new ToyProgramBuilder(name, true, ProgramBuilder._TOY);
|
ToyProgramBuilder builder = new ToyProgramBuilder(name, true, ProgramBuilder._TOY);
|
||||||
builder.createMemory("test1", "0x01001000", 0x8000);
|
builder.createMemory("test1", "0x01001000", 0x8000);
|
||||||
|
@ -133,9 +122,6 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
resetOptions();
|
resetOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see TestCase#tearDown()
|
|
||||||
*/
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
env.dispose();
|
env.dispose();
|
||||||
|
@ -174,12 +160,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
goToAddr(toolTwo, 0x1004700);
|
goToAddr(toolTwo, 0x1004700);
|
||||||
click2();
|
click2();
|
||||||
|
|
||||||
// paste
|
paste(toolTwo);
|
||||||
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
|
|
||||||
DockingActionIf pasteAction = getAction(plugin, "Paste");
|
|
||||||
assertEnabled(pasteAction);
|
|
||||||
performAction(pasteAction, true);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
// function FUN_01004700 should be renamed to "ghidra"
|
// function FUN_01004700 should be renamed to "ghidra"
|
||||||
CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class);
|
CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class);
|
||||||
|
@ -219,12 +200,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
goToAddr(toolTwo, entryAddr);
|
goToAddr(toolTwo, entryAddr);
|
||||||
click2();
|
click2();
|
||||||
|
|
||||||
// paste
|
paste(toolTwo);
|
||||||
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
|
|
||||||
DockingActionIf pasteAction = getAction(plugin, "Paste");
|
|
||||||
assertEnabled(pasteAction);
|
|
||||||
performAction(pasteAction, true);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class);
|
CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class);
|
||||||
cb.goToField(entryAddr, PlateFieldFactory.FIELD_NAME, 0, 0);
|
cb.goToField(entryAddr, PlateFieldFactory.FIELD_NAME, 0, 0);
|
||||||
|
@ -283,12 +259,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
goToAddr(toolTwo, addr);
|
goToAddr(toolTwo, addr);
|
||||||
click2();
|
click2();
|
||||||
|
|
||||||
// paste
|
paste(toolTwo);
|
||||||
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
|
|
||||||
DockingActionIf pasteAction = getAction(plugin, "Paste");
|
|
||||||
assertEnabled(pasteAction);
|
|
||||||
performAction(pasteAction, true);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
// verify the code browser field shows the comment
|
// verify the code browser field shows the comment
|
||||||
func = programTwo.getListing().getFunctionAt(addr);
|
func = programTwo.getListing().getFunctionAt(addr);
|
||||||
|
@ -338,12 +309,8 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
addr = getAddr(programTwo, 0x01004260);
|
addr = getAddr(programTwo, 0x01004260);
|
||||||
goToAddr(toolTwo, addr);
|
goToAddr(toolTwo, addr);
|
||||||
click2();
|
click2();
|
||||||
// paste
|
|
||||||
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
|
paste(toolTwo);
|
||||||
DockingActionIf pasteAction = getAction(plugin, "Paste");
|
|
||||||
assertEnabled(pasteAction);
|
|
||||||
performAction(pasteAction, true);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
// verify the code browser field shows the comment
|
// verify the code browser field shows the comment
|
||||||
func = programTwo.getListing().getFunctionAt(addr);
|
func = programTwo.getListing().getFunctionAt(addr);
|
||||||
|
@ -416,12 +383,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
goToAddr(toolTwo, 0x0100176f);
|
goToAddr(toolTwo, 0x0100176f);
|
||||||
click2();
|
click2();
|
||||||
|
|
||||||
// paste
|
paste(toolTwo);
|
||||||
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
|
|
||||||
DockingActionIf pasteAction = getAction(plugin, "Paste");
|
|
||||||
assertEnabled(pasteAction);
|
|
||||||
performAction(pasteAction, true);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
addr = getAddr(programTwo, 0x0100176f);
|
addr = getAddr(programTwo, 0x0100176f);
|
||||||
// nothing should happen with the stack variable comments
|
// nothing should happen with the stack variable comments
|
||||||
|
@ -433,7 +395,36 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
assertEquals(1, f.getNumRows());
|
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<ClipboardContentProviderService, List<DockingAction>> map =
|
||||||
|
(Map<ClipboardContentProviderService, List<DockingAction>>) getInstanceField(
|
||||||
|
"serviceActionMap", plugin);
|
||||||
|
List<DockingAction> list = map.get(service);
|
||||||
|
for (DockingAction pluginAction : list) {
|
||||||
|
if (pluginAction.getName().equals(actionName)) {
|
||||||
|
return pluginAction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void setupTool(PluginTool tool) throws Exception {
|
private void setupTool(PluginTool tool) throws Exception {
|
||||||
tool.addPlugin(ClipboardPlugin.class.getName());
|
tool.addPlugin(ClipboardPlugin.class.getName());
|
||||||
|
@ -511,10 +502,9 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
ClipboardPlugin clipboardPlugin) {
|
ClipboardPlugin clipboardPlugin) {
|
||||||
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
|
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
|
||||||
Set<?> keySet = serviceMap.keySet();
|
Set<?> keySet = serviceMap.keySet();
|
||||||
for (Object name : keySet) {
|
for (Object service : keySet) {
|
||||||
ClipboardContentProviderService service = (ClipboardContentProviderService) name;
|
if (service.getClass().equals(CodeBrowserClipboardProvider.class)) {
|
||||||
if (service instanceof CodeBrowserClipboardProvider) {
|
return (ClipboardContentProviderService) service;
|
||||||
return service;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -58,7 +58,6 @@ public class ByteViewerClipboardProvider extends ByteCopier
|
||||||
PluginTool tool) {
|
PluginTool tool) {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
currentProgram = provider.getProgram();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -80,11 +79,6 @@ public class ByteViewerClipboardProvider extends ByteCopier
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean paste(Transferable pasteData) {
|
public boolean paste(Transferable pasteData) {
|
||||||
if (!supportsPasteTransferable(pasteData)) {
|
|
||||||
tool.setStatusInfo("Paste failed: No valid data on clipboard", true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// try the default paste
|
// try the default paste
|
||||||
return pasteBytes(pasteData);
|
return pasteBytes(pasteData);
|
||||||
|
@ -106,42 +100,17 @@ public class ByteViewerClipboardProvider extends ByteCopier
|
||||||
@Override
|
@Override
|
||||||
public Transferable copy(TaskMonitor monitor) {
|
public Transferable copy(TaskMonitor monitor) {
|
||||||
String byteString = copyBytesAsString(currentSelection, true, monitor);
|
String byteString = copyBytesAsString(currentSelection, true, monitor);
|
||||||
String textSelection = provider.getCurrentTextSelection();
|
String textSelection = getTextSelection();
|
||||||
return new ByteViewerTransferable(byteString, textSelection);
|
return new ByteStringTransferable(byteString, textSelection);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getTextSelection() {
|
||||||
|
return provider.getCurrentTextSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor) {
|
public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor) {
|
||||||
|
return copyBytes(copyType, 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 if (copyType == PYTHON_BYTE_STRING_TYPE) {
|
|
||||||
byteString = "b'"
|
|
||||||
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", "\\x") + "'";
|
|
||||||
}
|
|
||||||
else if (copyType == PYTHON_LIST_TYPE) {
|
|
||||||
byteString = "[ "
|
|
||||||
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", ", 0x") + " ]";
|
|
||||||
}
|
|
||||||
else if (copyType == CPP_BYTE_ARRAY_TYPE) {
|
|
||||||
byteString = "{ "
|
|
||||||
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", ", 0x") + " }";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ByteViewerTransferable(byteString);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSelection(ProgramSelection selection) {
|
|
||||||
currentSelection = selection;
|
|
||||||
updateEnablement();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateEnablement() {
|
private void updateEnablement() {
|
||||||
|
@ -153,6 +122,11 @@ public class ByteViewerClipboardProvider extends ByteCopier
|
||||||
currentLocation = location;
|
currentLocation = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setSelection(ProgramSelection selection) {
|
||||||
|
currentSelection = selection;
|
||||||
|
updateEnablement();
|
||||||
|
}
|
||||||
|
|
||||||
void setProgram(Program p) {
|
void setProgram(Program p) {
|
||||||
currentProgram = p;
|
currentProgram = p;
|
||||||
currentLocation = null;
|
currentLocation = null;
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,16 +16,22 @@
|
||||||
package help.screenshot;
|
package help.screenshot;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import docking.DialogComponentProvider;
|
import docking.DialogComponentProvider;
|
||||||
|
import docking.action.DockingAction;
|
||||||
|
import docking.action.DockingActionIf;
|
||||||
import docking.widgets.fieldpanel.FieldPanel;
|
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.CodeBrowserPlugin;
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||||
|
import ghidra.app.services.ClipboardContentProviderService;
|
||||||
import ghidra.app.util.viewer.field.MnemonicFieldFactory;
|
import ghidra.app.util.viewer.field.MnemonicFieldFactory;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
@ -33,10 +39,6 @@ import ghidra.util.exception.AssertException;
|
||||||
|
|
||||||
public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator {
|
public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator {
|
||||||
|
|
||||||
public ClipboardPluginScreenShots() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCaptureCopySpecial() {
|
public void testCaptureCopySpecial() {
|
||||||
|
|
||||||
|
@ -47,9 +49,12 @@ public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator {
|
||||||
makeSelection(0x401000, 0x401000);
|
makeSelection(0x401000, 0x401000);
|
||||||
|
|
||||||
sel = cb.getCurrentSelection();
|
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();
|
captureDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +189,39 @@ public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator {
|
||||||
crop(imageBounds);
|
crop(imageBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showCopySpecialDialog() {
|
private CopyPasteSpecialDialog showCopySpecialDialog() {
|
||||||
performAction("Copy Special", "ClipboardPlugin", false);
|
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<DockingAction> actionList = (List<DockingAction>) actionsByService.get(service);
|
||||||
|
for (DockingAction pluginAction : actionList) {
|
||||||
|
if (pluginAction.getName().equals(actionName)) {
|
||||||
|
return pluginAction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue