mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-08-27 21:10:26 +00:00
GP-3512 - Created 'ListenerSet' for improved listener usage
This commit is contained in:
parent
41076f3af0
commit
08a900afad
|
@ -42,7 +42,7 @@ public class DbgDetachCommand extends AbstractDbgCommand<Void> {
|
|||
manager.fireThreadExited(t.getId(), process, pending);
|
||||
t.remove();
|
||||
}
|
||||
manager.getEventListeners().fire.processRemoved(process.getId(), DbgCause.Causes.UNCLAIMED);
|
||||
manager.getEventListeners().invoke().processRemoved(process.getId(), DbgCause.Causes.UNCLAIMED);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ public class DbgDebugInputCallbacks implements DebugInputCallbacks {
|
|||
|
||||
@Override
|
||||
public void startInput(long bufsize) {
|
||||
manager.getEventListeners().fire.promptChanged(">>>");
|
||||
manager.getEventListeners().invoke().promptChanged(">>>");
|
||||
CompletableFuture<String> cf = new CompletableFuture<String>();
|
||||
try {
|
||||
manager.setContinuation(cf);
|
||||
|
|
|
@ -206,7 +206,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
private final Map<Class<?>, DebugStatus> statusMap = new LinkedHashMap<>();
|
||||
private final Map<String, DebugStatus> statusByNameMap = new LinkedHashMap<>();
|
||||
private final ListenerSet<DbgEventsListener> listenersEvent =
|
||||
new ListenerSet<>(DbgEventsListener.class);
|
||||
new ListenerSet<>(DbgEventsListener.class, true);
|
||||
|
||||
private DebugEventInformation lastEventInformation;
|
||||
private DbgSession currentSession;
|
||||
|
@ -251,8 +251,8 @@ public class DbgManagerImpl implements DbgManager {
|
|||
thread.add();
|
||||
if (fire) {
|
||||
Causes cause = DbgCause.Causes.UNCLAIMED;
|
||||
getEventListeners().fire.threadCreated(thread, cause);
|
||||
getEventListeners().fire.threadSelected(thread, null, cause);
|
||||
getEventListeners().invoke().threadCreated(thread, cause);
|
||||
getEventListeners().invoke().threadSelected(thread, null, cause);
|
||||
}
|
||||
return threads.get(id);
|
||||
}
|
||||
|
@ -294,7 +294,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
for (DebugThreadId tid : toRemove) {
|
||||
removeThread(tid);
|
||||
}
|
||||
getEventListeners().fire.processRemoved(id, cause);
|
||||
getEventListeners().invoke().processRemoved(id, cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,7 +303,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
*
|
||||
* @param process the process that now has focus
|
||||
* @param cause the cause of the focus change
|
||||
* @param fire signal listeners
|
||||
* @param forEach() signal listeners
|
||||
* @return success status
|
||||
*/
|
||||
@Override
|
||||
|
@ -326,7 +326,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
DbgProcessImpl process = new DbgProcessImpl(this, id, pid, name);
|
||||
process.add();
|
||||
if (fire) {
|
||||
getEventListeners().fire.processAdded(process, DbgCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().processAdded(process, DbgCause.Causes.UNCLAIMED);
|
||||
}
|
||||
return processes.get(id);
|
||||
}
|
||||
|
@ -344,7 +344,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
if (sessions.remove(id) == null) {
|
||||
throw new IllegalArgumentException("There is no session with id " + id);
|
||||
}
|
||||
getEventListeners().fire.sessionRemoved(id, cause);
|
||||
getEventListeners().invoke().sessionRemoved(id, cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -366,7 +366,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
DbgSessionImpl session = new DbgSessionImpl(this, id);
|
||||
session.add();
|
||||
if (fire) {
|
||||
getEventListeners().fire.sessionAdded(session, DbgCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().sessionAdded(session, DbgCause.Causes.UNCLAIMED);
|
||||
}
|
||||
}
|
||||
return sessions.get(id);
|
||||
|
@ -748,8 +748,8 @@ public class DbgManagerImpl implements DbgManager {
|
|||
|
||||
DebugBreakpoint bp = evt.getInfo();
|
||||
DbgBreakpointInfo info = new DbgBreakpointInfo(bp, getEventProcess(), getEventThread());
|
||||
getEventListeners().fire.threadSelected(eventThread, null, evt.getCause());
|
||||
getEventListeners().fire.breakpointHit(info, evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(eventThread, null, evt.getCause());
|
||||
getEventListeners().invoke().breakpointHit(info, evt.getCause());
|
||||
|
||||
String key = Integer.toHexString(bp.getId());
|
||||
if (statusByNameMap.containsKey(key)) {
|
||||
|
@ -766,8 +766,8 @@ public class DbgManagerImpl implements DbgManager {
|
|||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processException(DbgExceptionEvent evt, Void v) {
|
||||
getEventListeners().fire.eventSelected(evt, evt.getCause());
|
||||
getEventListeners().fire.threadSelected(eventThread, null, evt.getCause());
|
||||
getEventListeners().invoke().eventSelected(evt, evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(eventThread, null, evt.getCause());
|
||||
|
||||
DebugExceptionRecord64 info = evt.getInfo();
|
||||
String key = Integer.toHexString(info.code);
|
||||
|
@ -789,9 +789,9 @@ public class DbgManagerImpl implements DbgManager {
|
|||
DbgProcessImpl process = getCurrentProcess();
|
||||
DbgThreadImpl thread = getThreadFromDebugProcessInfo(process, evt.getInfo());
|
||||
|
||||
getEventListeners().fire.eventSelected(evt, evt.getCause());
|
||||
getEventListeners().fire.threadCreated(thread, DbgCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
|
||||
getEventListeners().invoke().eventSelected(evt, evt.getCause());
|
||||
getEventListeners().invoke().threadCreated(thread, DbgCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
|
||||
|
||||
String key = eventId.id();
|
||||
if (statusByNameMap.containsKey(key)) {
|
||||
|
@ -815,8 +815,8 @@ public class DbgManagerImpl implements DbgManager {
|
|||
thread.remove();
|
||||
}
|
||||
process.threadExited(eventId);
|
||||
getEventListeners().fire.eventSelected(evt, evt.getCause());
|
||||
getEventListeners().fire.threadExited(eventId, process, evt.getCause());
|
||||
getEventListeners().invoke().eventSelected(evt, evt.getCause());
|
||||
getEventListeners().invoke().threadExited(eventId, process, evt.getCause());
|
||||
|
||||
String key = eventId.id();
|
||||
if (statusByNameMap.containsKey(key)) {
|
||||
|
@ -842,7 +842,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
|
||||
//currentThread = evt.getThread();
|
||||
currentThread.setState(evt.getState(), evt.getCause(), evt.getReason());
|
||||
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
|
||||
|
||||
String key = eventId.id();
|
||||
if (statusByNameMap.containsKey(key)) {
|
||||
|
@ -861,9 +861,9 @@ public class DbgManagerImpl implements DbgManager {
|
|||
protected DebugStatus processProcessCreated(DbgProcessCreatedEvent evt, Void v) {
|
||||
DebugProcessInfo info = evt.getInfo();
|
||||
DbgProcessImpl proc = getProcessFromDebugProcessInfo(info);
|
||||
getEventListeners().fire.eventSelected(evt, evt.getCause());
|
||||
getEventListeners().fire.processAdded(proc, evt.getCause());
|
||||
getEventListeners().fire.processSelected(proc, evt.getCause());
|
||||
getEventListeners().invoke().eventSelected(evt, evt.getCause());
|
||||
getEventListeners().invoke().processAdded(proc, evt.getCause());
|
||||
getEventListeners().invoke().processSelected(proc, evt.getCause());
|
||||
|
||||
getThreadFromDebugProcessInfo(proc, info.initialThreadInfo);
|
||||
//getEventListeners().fire.threadCreated(thread, evt.getCause());
|
||||
|
@ -892,9 +892,9 @@ public class DbgManagerImpl implements DbgManager {
|
|||
DbgProcessImpl process = getCurrentProcess();
|
||||
process.setExitCode(Long.valueOf(evt.getInfo()));
|
||||
|
||||
getEventListeners().fire.eventSelected(evt, evt.getCause());
|
||||
getEventListeners().fire.threadExited(eventId, process, evt.getCause());
|
||||
getEventListeners().fire.processExited(process, evt.getCause());
|
||||
getEventListeners().invoke().eventSelected(evt, evt.getCause());
|
||||
getEventListeners().invoke().threadExited(eventId, process, evt.getCause());
|
||||
getEventListeners().invoke().processExited(process, evt.getCause());
|
||||
|
||||
for (DebugBreakpoint bpt : getBreakpoints()) {
|
||||
breaksById.remove(bpt.getId());
|
||||
|
@ -903,7 +903,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
thread.remove();
|
||||
}
|
||||
process.remove(evt.getCause());
|
||||
getEventListeners().fire.processRemoved(process.getId(), evt.getCause());
|
||||
getEventListeners().invoke().processRemoved(process.getId(), evt.getCause());
|
||||
|
||||
String key = process.getId().id();
|
||||
if (statusByNameMap.containsKey(key)) {
|
||||
|
@ -923,7 +923,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
DebugThreadId eventId = updateState();
|
||||
|
||||
currentProcess = evt.getProcess();
|
||||
getEventListeners().fire.processSelected(currentProcess, evt.getCause());
|
||||
getEventListeners().invoke().processSelected(currentProcess, evt.getCause());
|
||||
|
||||
String key = eventId.id();
|
||||
if (statusByNameMap.containsKey(key)) {
|
||||
|
@ -944,8 +944,8 @@ public class DbgManagerImpl implements DbgManager {
|
|||
DbgProcessImpl process = getCurrentProcess();
|
||||
DebugModuleInfo info = evt.getInfo();
|
||||
process.moduleLoaded(info);
|
||||
getEventListeners().fire.eventSelected(evt, evt.getCause());
|
||||
getEventListeners().fire.moduleLoaded(process, info, evt.getCause());
|
||||
getEventListeners().invoke().eventSelected(evt, evt.getCause());
|
||||
getEventListeners().invoke().moduleLoaded(process, info, evt.getCause());
|
||||
|
||||
String key = info.getModuleName();
|
||||
if (statusByNameMap.containsKey(key)) {
|
||||
|
@ -966,8 +966,8 @@ public class DbgManagerImpl implements DbgManager {
|
|||
DbgProcessImpl process = getCurrentProcess();
|
||||
DebugModuleInfo info = evt.getInfo();
|
||||
process.moduleUnloaded(info);
|
||||
getEventListeners().fire.eventSelected(evt, evt.getCause());
|
||||
getEventListeners().fire.moduleUnloaded(process, info, evt.getCause());
|
||||
getEventListeners().invoke().eventSelected(evt, evt.getCause());
|
||||
getEventListeners().invoke().moduleUnloaded(process, info, evt.getCause());
|
||||
|
||||
String key = info.getModuleName();
|
||||
if (statusByNameMap.containsKey(key)) {
|
||||
|
@ -1012,7 +1012,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
//System.err.println("RUNNING " + id);
|
||||
dbgState = DbgState.RUNNING;
|
||||
// NB: Needed by GADP variants, but not IN-VM
|
||||
getEventListeners().fire.memoryChanged(currentProcess, 0L, 0,
|
||||
getEventListeners().invoke().memoryChanged(currentProcess, 0L, 0,
|
||||
evt.getCause());
|
||||
processEvent(new DbgRunningEvent(eventThread.getId()));
|
||||
}
|
||||
|
@ -1056,7 +1056,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
if (key.value() == id) {
|
||||
DbgThread thread = getThread(key);
|
||||
if (thread != null) {
|
||||
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
|
||||
}
|
||||
processEvent(new DbgPromptChangedEvent(getControl().getPromptText()));
|
||||
break;
|
||||
|
@ -1080,7 +1080,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
DebugThreadId eventId = updateState();
|
||||
|
||||
currentSession = evt.getSession();
|
||||
getEventListeners().fire.sessionSelected(currentSession, evt.getCause());
|
||||
getEventListeners().invoke().sessionSelected(currentSession, evt.getCause());
|
||||
|
||||
String key = eventId.id();
|
||||
if (statusByNameMap.containsKey(key)) {
|
||||
|
@ -1118,23 +1118,23 @@ public class DbgManagerImpl implements DbgManager {
|
|||
|
||||
protected void processDebuggeeStateChanged(DbgDebuggeeStateChangeEvent evt, Void v) {
|
||||
if (evt.getFlags().contains(ChangeDebuggeeState.DATA)) {
|
||||
getEventListeners().fire.memoryChanged(currentProcess, 0L, 0, evt.getCause());
|
||||
getEventListeners().invoke().memoryChanged(currentProcess, 0L, 0, evt.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
protected void processSystemErrorEvent(DbgSystemErrorEvent evt, Void v) {
|
||||
getEventListeners().fire.eventSelected(evt, evt.getCause());
|
||||
getEventListeners().invoke().eventSelected(evt, evt.getCause());
|
||||
String error = "SystemError " + evt.getError() + ":" + evt.getLevel();
|
||||
getEventListeners().fire.consoleOutput(error, 0);
|
||||
getEventListeners().invoke().consoleOutput(error, 0);
|
||||
}
|
||||
|
||||
protected void processConsoleOutput(DbgConsoleOutputEvent evt, Void v) {
|
||||
getEventListeners().fire.eventSelected(evt, evt.getCause());
|
||||
getEventListeners().fire.consoleOutput(evt.getInfo(), evt.getMask());
|
||||
getEventListeners().invoke().eventSelected(evt, evt.getCause());
|
||||
getEventListeners().invoke().consoleOutput(evt.getInfo(), evt.getMask());
|
||||
}
|
||||
|
||||
protected void processPromptChanged(DbgPromptChangedEvent evt, Void v) {
|
||||
getEventListeners().fire.promptChanged(evt.getPrompt());
|
||||
getEventListeners().invoke().promptChanged(evt.getPrompt());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1215,7 +1215,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
@Internal
|
||||
public void doBreakpointCreated(DbgBreakpointInfo newInfo, DbgCause cause) {
|
||||
addKnownBreakpoint(newInfo, true);
|
||||
getEventListeners().fire.breakpointCreated(newInfo, cause);
|
||||
getEventListeners().invoke().breakpointCreated(newInfo, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1227,7 +1227,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
@Internal
|
||||
public void doBreakpointModified(DbgBreakpointInfo newInfo, DbgCause cause) {
|
||||
DbgBreakpointInfo oldInfo = addKnownBreakpoint(newInfo, true);
|
||||
getEventListeners().fire.breakpointModified(newInfo, oldInfo, cause);
|
||||
getEventListeners().invoke().breakpointModified(newInfo, oldInfo, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1242,7 +1242,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
if (oldInfo == null) {
|
||||
return;
|
||||
}
|
||||
getEventListeners().fire.breakpointDeleted(oldInfo, cause);
|
||||
getEventListeners().invoke().breakpointDeleted(oldInfo, cause);
|
||||
oldInfo.dispose();
|
||||
}
|
||||
|
||||
|
@ -1251,7 +1251,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
if (Objects.equals(newInfo, oldInfo)) {
|
||||
return;
|
||||
}
|
||||
getEventListeners().fire.breakpointModified(newInfo, oldInfo, cause);
|
||||
getEventListeners().invoke().breakpointModified(newInfo, oldInfo, cause);
|
||||
}
|
||||
|
||||
@Internal
|
||||
|
@ -1637,7 +1637,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
}
|
||||
|
||||
public void fireThreadExited(DebugThreadId id, DbgProcessImpl process, DbgCause cause) {
|
||||
getEventListeners().fire.threadExited(id, process, cause);
|
||||
getEventListeners().invoke().threadExited(id, process, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1771,7 +1771,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
if (mirror != null) {
|
||||
mirror.setOffset(currentProcess.getOffset());
|
||||
currentProcess = eventProcess = mirror;
|
||||
getEventListeners().fire.processSelected(eventProcess, Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().processSelected(eventProcess, Causes.UNCLAIMED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1786,7 +1786,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
if (mirror != null) {
|
||||
mirror.setOffset(currentThread.getOffset());
|
||||
currentThread = eventThread = mirror;
|
||||
getEventListeners().fire.threadSelected(eventThread, null, Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().threadSelected(eventThread, null, Causes.UNCLAIMED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1795,7 +1795,7 @@ public class DbgManagerImpl implements DbgManager {
|
|||
eventProcess = getProcessComputeIfAbsent(epid, so.getCurrentProcessSystemId(), so.getCurrentProcessExecutableName(), true);
|
||||
currentThread = eventThread = getThreadComputeIfAbsent(etid, (DbgProcessImpl) eventProcess,
|
||||
so.getCurrentThreadSystemId(), false);
|
||||
getEventListeners().fire.threadSelected(eventThread, null, Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().threadSelected(eventThread, null, Causes.UNCLAIMED);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ public class DbgModuleImpl implements DbgModule {
|
|||
*/
|
||||
public void add() {
|
||||
process.addModule(this);
|
||||
manager.getEventListeners().fire.moduleLoaded(process, info, Causes.UNCLAIMED);
|
||||
manager.getEventListeners().invoke().moduleLoaded(process, info, Causes.UNCLAIMED);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,7 +66,7 @@ public class DbgModuleImpl implements DbgModule {
|
|||
*/
|
||||
public void remove() {
|
||||
process.removeModule(name);
|
||||
manager.getEventListeners().fire.moduleUnloaded(process, info, Causes.UNCLAIMED);
|
||||
manager.getEventListeners().invoke().moduleUnloaded(process, info, Causes.UNCLAIMED);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -93,7 +93,7 @@ public class DbgThreadImpl implements DbgThread {
|
|||
//manager.getEventListeners().fire.threadCreated(this, DbgCause.Causes.UNCLAIMED);
|
||||
process.addThread(this);
|
||||
state.addChangeListener((oldState, newState, pair) -> {
|
||||
this.manager.getEventListeners().fire.threadStateChanged(this, newState, pair.cause,
|
||||
this.manager.getEventListeners().invoke().threadStateChanged(this, newState, pair.cause,
|
||||
pair.reason);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -174,12 +174,12 @@ public interface DbgModelTargetBreakpointSpec extends //
|
|||
|
||||
/**
|
||||
* Update the enabled field
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* This does not actually toggle the breakpoint. It just updates the field and calls the proper
|
||||
* listeners. To actually toggle the breakpoint, use {@link #toggle(boolean)} instead, which if
|
||||
* effective, should eventually cause this method to be called.
|
||||
*
|
||||
*
|
||||
* @param enabled true if enabled, false if disabled
|
||||
* @param reason a description of the cause (not really used, yet)
|
||||
*/
|
||||
|
@ -207,8 +207,8 @@ public interface DbgModelTargetBreakpointSpec extends //
|
|||
public default void breakpointHit() {
|
||||
DbgModelTargetThread targetThread =
|
||||
getParentProcess().getThreads().getTargetThread(getManager().getEventThread());
|
||||
getActions().fire.breakpointHit((DbgModelTargetBreakpointSpec) getProxy(), targetThread,
|
||||
null, this);
|
||||
getActions().invoke()
|
||||
.breakpointHit((DbgModelTargetBreakpointSpec) getProxy(), targetThread, null, this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, Target
|
|||
getParentThread().getThread().writeRegisters(toWrite).handle(seq::next);
|
||||
// TODO: Should probably filter only effective and normalized writes in the callback
|
||||
}).then(seq -> {
|
||||
manager.getEventListeners().fire.threadStateChanged(thread, thread.getState(),
|
||||
manager.getEventListeners().invoke().threadStateChanged(thread, thread.getState(),
|
||||
DbgCause.Causes.UNCLAIMED, DbgReason.Reasons.NONE);
|
||||
broadcast().registersUpdated(getProxy(), values);
|
||||
seq.exit();
|
||||
|
|
|
@ -110,7 +110,7 @@ public class DbgModelImpl extends AbstractDbgModel implements DebuggerObjectMode
|
|||
|
||||
@Override
|
||||
public void terminate() throws IOException {
|
||||
listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL);
|
||||
listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL);
|
||||
root.invalidateSubtree(root, "Dbgeng is terminating");
|
||||
dbg.terminate();
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
*/
|
||||
package agent.dbgeng.model.impl;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.dbgeng.manager.breakpoint.DbgBreakpointInfo;
|
||||
|
@ -27,33 +28,21 @@ import ghidra.dbg.target.TargetBreakpointSpec;
|
|||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "BreakpointSpec",
|
||||
attributes = { //
|
||||
@TargetAttributeType( //
|
||||
@TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = { //
|
||||
@TargetAttributeType( //
|
||||
name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, //
|
||||
type = DbgModelTargetBreakpointContainerImpl.class), //
|
||||
@TargetAttributeType( //
|
||||
@TargetAttributeType( //
|
||||
name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, //
|
||||
type = DbgModelTargetBreakpointSpecImpl.class), //
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetBreakpointSpecImpl.BPT_TYPE_ATTRIBUTE_NAME,
|
||||
type = String.class), //
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetBreakpointSpecImpl.BPT_DISP_ATTRIBUTE_NAME,
|
||||
type = String.class), //
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetBreakpointSpecImpl.BPT_PENDING_ATTRIBUTE_NAME,
|
||||
type = String.class), //
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetBreakpointSpecImpl.BPT_TIMES_ATTRIBUTE_NAME,
|
||||
type = Integer.class), //
|
||||
@TargetAttributeType(type = Void.class) //
|
||||
},
|
||||
canonicalContainer = true)
|
||||
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_TYPE_ATTRIBUTE_NAME, type = String.class), //
|
||||
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_DISP_ATTRIBUTE_NAME, type = String.class), //
|
||||
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_PENDING_ATTRIBUTE_NAME, type = String.class), //
|
||||
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_TIMES_ATTRIBUTE_NAME, type = Integer.class), //
|
||||
@TargetAttributeType(type = Void.class) //
|
||||
}, canonicalContainer = true)
|
||||
public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl
|
||||
implements DbgModelTargetBreakpointSpec {
|
||||
|
||||
|
@ -69,28 +58,21 @@ public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl
|
|||
protected boolean enabled;
|
||||
|
||||
public void changeAttributeSet(String reason) {
|
||||
this.changeAttributes(List.of(), List.of(), Map.of(
|
||||
DISPLAY_ATTRIBUTE_NAME, "[" + info.getNumber() + "] " + info.getExpression(),
|
||||
RANGE_ATTRIBUTE_NAME, doGetRange(),
|
||||
SPEC_ATTRIBUTE_NAME, this,
|
||||
EXPRESSION_ATTRIBUTE_NAME, info.getExpression(),
|
||||
KINDS_ATTRIBUTE_NAME, getKinds(),
|
||||
this.changeAttributes(List.of(), List.of(),
|
||||
Map.of(DISPLAY_ATTRIBUTE_NAME, "[" + info.getNumber() + "] " + info.getExpression(),
|
||||
RANGE_ATTRIBUTE_NAME, doGetRange(), SPEC_ATTRIBUTE_NAME, this,
|
||||
EXPRESSION_ATTRIBUTE_NAME, info.getExpression(), KINDS_ATTRIBUTE_NAME, getKinds(),
|
||||
|
||||
BPT_TYPE_ATTRIBUTE_NAME, info.getType().name(),
|
||||
BPT_DISP_ATTRIBUTE_NAME, info.getDisp().name(),
|
||||
BPT_PENDING_ATTRIBUTE_NAME, info.getPending(),
|
||||
BPT_TIMES_ATTRIBUTE_NAME, info.getTimes()),
|
||||
BPT_TYPE_ATTRIBUTE_NAME, info.getType().name(), BPT_DISP_ATTRIBUTE_NAME,
|
||||
info.getDisp().name(), BPT_PENDING_ATTRIBUTE_NAME, info.getPending(),
|
||||
BPT_TIMES_ATTRIBUTE_NAME, info.getTimes()),
|
||||
reason);
|
||||
}
|
||||
|
||||
// Use strong references on actions
|
||||
// The values may be weak, but the keys, which are the same objects, are strong
|
||||
private final ListenerSet<TargetBreakpointAction> actions =
|
||||
new ListenerSet<>(TargetBreakpointAction.class) {
|
||||
// Use strong references on actions
|
||||
// The values may be weak, but the keys, which are the same objects, are strong
|
||||
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
};
|
||||
new ListenerSet<>(TargetBreakpointAction.class, false);
|
||||
|
||||
public DbgModelTargetBreakpointSpecImpl(DbgModelTargetBreakpointContainer breakpoints,
|
||||
DbgBreakpointInfo info) {
|
||||
|
@ -128,11 +110,11 @@ public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl
|
|||
|
||||
/**
|
||||
* Update the enabled field
|
||||
*
|
||||
*
|
||||
* This does not actually toggle the breakpoint. It just updates the field and calls the proper
|
||||
* listeners. To actually toggle the breakpoint, use {@link #toggle(boolean)} instead, which if
|
||||
* effective, should eventually cause this method to be called.
|
||||
*
|
||||
*
|
||||
* @param enabled true if enabled, false if disabled
|
||||
* @param reason a description of the cause (not really used, yet)
|
||||
*/
|
||||
|
|
|
@ -109,8 +109,10 @@ public class DbgModelTargetExceptionImpl extends DbgModelTargetObjectImpl
|
|||
.setFocus(this);
|
||||
changeAttributes(List.of(), List.of(), Map.of( //
|
||||
MODIFIED_ATTRIBUTE_NAME, true), "Refreshed");
|
||||
manager.getEventListeners().fire.consoleOutput(
|
||||
"Exception " + filter.getExceptionCode() + " : " + filter.getName() + "\n", 0);
|
||||
manager.getEventListeners()
|
||||
.invoke()
|
||||
.consoleOutput("Exception " + filter.getExceptionCode() + " : " +
|
||||
filter.getName() + "\n", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,7 +162,7 @@ public class DbgModelTargetRegisterContainerImpl extends DbgModelTargetObjectImp
|
|||
return thread.writeRegisters(toWrite);
|
||||
// TODO: Should probably filter only effective and normalized writes in the callback
|
||||
}).thenAccept(__ -> {
|
||||
manager.getEventListeners().fire.threadStateChanged(thread, thread.getState(),
|
||||
manager.getEventListeners().invoke().threadStateChanged(thread, thread.getState(),
|
||||
DbgCause.Causes.UNCLAIMED, DbgReason.Reasons.NONE);
|
||||
broadcast().registersUpdated(getProxy(), values);
|
||||
}));
|
||||
|
|
|
@ -121,7 +121,7 @@ public class DbgModel2Impl extends AbstractDbgModel
|
|||
|
||||
@Override
|
||||
public void terminate() throws IOException {
|
||||
listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL);
|
||||
listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL);
|
||||
root.invalidateSubtree(root, "Dbgmodel is terminating");
|
||||
dbg.terminate();
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
|
|||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointAction;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl implements //
|
||||
|
@ -69,79 +68,81 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
|||
}
|
||||
}
|
||||
|
||||
protected static Class<? extends DbgModelTargetObject> lookupWrapperType(String type, String parentName) {
|
||||
protected static Class<? extends DbgModelTargetObject> lookupWrapperType(String type,
|
||||
String parentName) {
|
||||
switch (type) {
|
||||
case "Available":
|
||||
return DbgModelTargetAvailableContainer.class;
|
||||
case "Sessions":
|
||||
return DbgModelTargetSessionContainer.class;
|
||||
case "Processes":
|
||||
return DbgModelTargetProcessContainer.class;
|
||||
case "Threads":
|
||||
return DbgModelTargetThreadContainer.class;
|
||||
case "Modules":
|
||||
return DbgModelTargetModuleContainer.class;
|
||||
case "Frames":
|
||||
return DbgModelTargetStack.class;
|
||||
case "Registers":
|
||||
return DbgModelTargetRegisterContainer.class;
|
||||
case "Attributes":
|
||||
return DbgModelTargetSessionAttributes.class;
|
||||
case "Breakpoints":
|
||||
return DbgModelTargetBreakpointContainer.class;
|
||||
case "cursession":
|
||||
return DbgModelTargetSession.class;
|
||||
case "curprocess":
|
||||
return DbgModelTargetProcess.class;
|
||||
case "curthread":
|
||||
return DbgModelTargetThread.class;
|
||||
case "curframe":
|
||||
return DbgModelTargetStackFrame.class;
|
||||
case "User":
|
||||
return DbgModelTargetRegisterBank.class;
|
||||
case "TTD":
|
||||
return DbgModelTargetTTD.class;
|
||||
case "Debug":
|
||||
return DbgModelTargetDebugContainer.class;
|
||||
case "Available":
|
||||
return DbgModelTargetAvailableContainer.class;
|
||||
case "Sessions":
|
||||
return DbgModelTargetSessionContainer.class;
|
||||
case "Processes":
|
||||
return DbgModelTargetProcessContainer.class;
|
||||
case "Threads":
|
||||
return DbgModelTargetThreadContainer.class;
|
||||
case "Modules":
|
||||
return DbgModelTargetModuleContainer.class;
|
||||
case "Frames":
|
||||
return DbgModelTargetStack.class;
|
||||
case "Registers":
|
||||
return DbgModelTargetRegisterContainer.class;
|
||||
case "Attributes":
|
||||
return DbgModelTargetSessionAttributes.class;
|
||||
case "Breakpoints":
|
||||
return DbgModelTargetBreakpointContainer.class;
|
||||
case "cursession":
|
||||
return DbgModelTargetSession.class;
|
||||
case "curprocess":
|
||||
return DbgModelTargetProcess.class;
|
||||
case "curthread":
|
||||
return DbgModelTargetThread.class;
|
||||
case "curframe":
|
||||
return DbgModelTargetStackFrame.class;
|
||||
case "User":
|
||||
return DbgModelTargetRegisterBank.class;
|
||||
case "TTD":
|
||||
return DbgModelTargetTTD.class;
|
||||
case "Debug":
|
||||
return DbgModelTargetDebugContainer.class;
|
||||
}
|
||||
if (parentName != null) {
|
||||
switch (parentName) {
|
||||
case "Available":
|
||||
return DbgModelTargetAvailable.class;
|
||||
case "Sessions":
|
||||
return DbgModelTargetSession.class;
|
||||
case "Processes":
|
||||
return DbgModelTargetProcess.class;
|
||||
case "Threads":
|
||||
return DbgModelTargetThread.class;
|
||||
case "Modules":
|
||||
return DbgModelTargetModule.class;
|
||||
case "Frames":
|
||||
return DbgModelTargetStackFrame.class;
|
||||
case "Breakpoints":
|
||||
return DbgModelTargetBreakpointSpec.class;
|
||||
// case "Registers":
|
||||
// return DbgModelTargetRegisterBank.class;
|
||||
case "FloatingPoint":
|
||||
case "Kernel":
|
||||
case "SIMD":
|
||||
case "VFP":
|
||||
case "User":
|
||||
return DbgModelTargetRegister.class;
|
||||
case "Available":
|
||||
return DbgModelTargetAvailable.class;
|
||||
case "Sessions":
|
||||
return DbgModelTargetSession.class;
|
||||
case "Processes":
|
||||
return DbgModelTargetProcess.class;
|
||||
case "Threads":
|
||||
return DbgModelTargetThread.class;
|
||||
case "Modules":
|
||||
return DbgModelTargetModule.class;
|
||||
case "Frames":
|
||||
return DbgModelTargetStackFrame.class;
|
||||
case "Breakpoints":
|
||||
return DbgModelTargetBreakpointSpec.class;
|
||||
// case "Registers":
|
||||
// return DbgModelTargetRegisterBank.class;
|
||||
case "FloatingPoint":
|
||||
case "Kernel":
|
||||
case "SIMD":
|
||||
case "VFP":
|
||||
case "User":
|
||||
return DbgModelTargetRegister.class;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static DbgModelTargetObject makeProxy(DbgModel2Impl model, DbgModelTargetObject parent, String key,
|
||||
ModelObject object) {
|
||||
public static DbgModelTargetObject makeProxy(DbgModel2Impl model, DbgModelTargetObject parent,
|
||||
String key, ModelObject object) {
|
||||
List<Class<? extends TargetObject>> mixins = new ArrayList<>();
|
||||
String lkey = key;
|
||||
String pname = parent.getName();
|
||||
|
||||
if (object.getKind().equals(ModelObjectKind.OBJECT_METHOD)) {
|
||||
mixins.add(DbgModelTargetMethod.class);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Class<? extends DbgModelTargetObject> mixin = lookupWrapperType(lkey, pname);
|
||||
if (mixin != null) {
|
||||
mixins.add(mixin);
|
||||
|
@ -161,12 +162,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
|||
private boolean breakpointEnabled;
|
||||
|
||||
private final ListenerSet<TargetBreakpointAction> breakpointActions =
|
||||
new ListenerSet<>(TargetBreakpointAction.class) {
|
||||
// Use strong references on actions
|
||||
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
};
|
||||
new ListenerSet<>(TargetBreakpointAction.class, false);
|
||||
|
||||
// Extending DefaultTargetObject may spare you from listeners, elements, and
|
||||
// attributes
|
||||
|
@ -175,8 +171,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
|||
|
||||
// any other fields you need to support your impl
|
||||
|
||||
public DelegateDbgModel2TargetObject(DbgModel2Impl model, DbgModelTargetObject parent, String key,
|
||||
ModelObject modelObject, List<Class<? extends TargetObject>> mixins) {
|
||||
public DelegateDbgModel2TargetObject(DbgModel2Impl model, DbgModelTargetObject parent,
|
||||
String key, ModelObject modelObject, List<Class<? extends TargetObject>> mixins) {
|
||||
super(model, mixins, model, parent.getProxy(), key, getHintForObject(modelObject));
|
||||
// System.err.println(this);
|
||||
this.state = new ProxyState(model, modelObject);
|
||||
|
@ -198,8 +194,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
|||
if (mixin != null) {
|
||||
mixins.add(mixin);
|
||||
}
|
||||
DelegateDbgModel2TargetObject delegate = new DelegateDbgModel2TargetObject(getModel(), p, key, modelObject,
|
||||
mixins);
|
||||
DelegateDbgModel2TargetObject delegate =
|
||||
new DelegateDbgModel2TargetObject(getModel(), p, key, modelObject, mixins);
|
||||
return delegate;
|
||||
}
|
||||
|
||||
|
@ -226,41 +222,41 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
|||
protected void checkExited(DbgState state, DbgCause cause) {
|
||||
TargetExecutionState exec = TargetExecutionState.INACTIVE;
|
||||
switch (state) {
|
||||
case NOT_STARTED: {
|
||||
exec = TargetExecutionState.INACTIVE;
|
||||
break;
|
||||
}
|
||||
case STARTING: {
|
||||
exec = TargetExecutionState.ALIVE;
|
||||
break;
|
||||
}
|
||||
case RUNNING: {
|
||||
exec = TargetExecutionState.RUNNING;
|
||||
resetModified();
|
||||
onRunning();
|
||||
break;
|
||||
}
|
||||
case STOPPED: {
|
||||
exec = TargetExecutionState.STOPPED;
|
||||
onStopped();
|
||||
break;
|
||||
}
|
||||
case EXIT: {
|
||||
exec = TargetExecutionState.TERMINATED;
|
||||
onExit();
|
||||
break;
|
||||
}
|
||||
case SESSION_EXIT: {
|
||||
getModel().close();
|
||||
return;
|
||||
}
|
||||
case NOT_STARTED: {
|
||||
exec = TargetExecutionState.INACTIVE;
|
||||
break;
|
||||
}
|
||||
case STARTING: {
|
||||
exec = TargetExecutionState.ALIVE;
|
||||
break;
|
||||
}
|
||||
case RUNNING: {
|
||||
exec = TargetExecutionState.RUNNING;
|
||||
resetModified();
|
||||
onRunning();
|
||||
break;
|
||||
}
|
||||
case STOPPED: {
|
||||
exec = TargetExecutionState.STOPPED;
|
||||
onStopped();
|
||||
break;
|
||||
}
|
||||
case EXIT: {
|
||||
exec = TargetExecutionState.TERMINATED;
|
||||
onExit();
|
||||
break;
|
||||
}
|
||||
case SESSION_EXIT: {
|
||||
getModel().close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (proxy instanceof TargetExecutionStateful) {
|
||||
if (proxy instanceof DbgModelTargetSession) {
|
||||
if (state != DbgState.EXIT) {
|
||||
setExecutionState(exec, "Refreshed");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
TargetExecutionState previous = this.getExecutionState();
|
||||
if (!previous.equals(TargetExecutionState.INACTIVE)) {
|
||||
|
@ -368,7 +364,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
|||
}
|
||||
if (proxy instanceof TargetAccessConditioned) {
|
||||
changeAttributes(List.of(), List.of(), Map.of( //
|
||||
TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME, accessible //
|
||||
TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME, accessible //
|
||||
), "Accessibility changed");
|
||||
}
|
||||
}
|
||||
|
@ -420,16 +416,18 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
|||
List<DelegateDbgModel2TargetObject> delegates = new ArrayList<>();
|
||||
TargetObject stack = (TargetObject) getCachedAttribute("Stack");
|
||||
if (stack != null) {
|
||||
DbgModelTargetStack frames = (DbgModelTargetStack) stack.getCachedAttribute("Frames");
|
||||
DbgModelTargetStack frames =
|
||||
(DbgModelTargetStack) stack.getCachedAttribute("Frames");
|
||||
delegates.add((DelegateDbgModel2TargetObject) frames.getDelegate());
|
||||
}
|
||||
DbgModelTargetRegisterContainer container = (DbgModelTargetRegisterContainer) getCachedAttribute(
|
||||
"Registers");
|
||||
DbgModelTargetRegisterContainer container =
|
||||
(DbgModelTargetRegisterContainer) getCachedAttribute("Registers");
|
||||
if (container == null) {
|
||||
return;
|
||||
}
|
||||
delegates.add((DelegateDbgModel2TargetObject) container.getDelegate());
|
||||
DbgModelTargetRegisterBank bank = (DbgModelTargetRegisterBank) container.getCachedAttribute("User");
|
||||
DbgModelTargetRegisterBank bank =
|
||||
(DbgModelTargetRegisterBank) container.getCachedAttribute("User");
|
||||
if (bank == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -457,7 +455,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
|||
for (TargetObject obj : getCachedElements().values()) {
|
||||
if (obj instanceof TargetStackFrame) {
|
||||
DbgModelTargetObject frame = (DbgModelTargetObject) obj;
|
||||
DelegateDbgModel2TargetObject delegate = (DelegateDbgModel2TargetObject) frame.getDelegate();
|
||||
DelegateDbgModel2TargetObject delegate =
|
||||
(DelegateDbgModel2TargetObject) frame.getDelegate();
|
||||
delegate.threadStateChangedSpecific(state, reason);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ public abstract class AbstractFridaCommand<T> implements FridaCommand<T> {
|
|||
String type = jobj.get("type").getAsString();
|
||||
if (type.equals("error")) {
|
||||
String desc = jobj.get("description").getAsString();
|
||||
manager.getEventListeners().fire.consoleOutput(desc+"\n", 0);
|
||||
manager.getEventListeners().invoke().consoleOutput(desc+"\n", 0);
|
||||
Msg.error(this, desc);
|
||||
return;
|
||||
}
|
||||
|
@ -88,14 +88,14 @@ public abstract class AbstractFridaCommand<T> implements FridaCommand<T> {
|
|||
if (jobj.has("payload")) {
|
||||
Object object = jobj.get("payload");
|
||||
if (!(object instanceof JsonPrimitive)) {
|
||||
manager.getEventListeners().fire.consoleOutput(object+" not a String\n", 0);
|
||||
manager.getEventListeners().invoke().consoleOutput(object+" not a String\n", 0);
|
||||
Msg.error(this, object);
|
||||
return;
|
||||
}
|
||||
|
||||
String value = ((JsonPrimitive) object).getAsString();
|
||||
if (!value.startsWith("{")) {
|
||||
manager.getEventListeners().fire.consoleOutput(object+"\n", 0);
|
||||
manager.getEventListeners().invoke().consoleOutput(object+"\n", 0);
|
||||
return;
|
||||
}
|
||||
JsonElement res = JsonParser.parseString(value);
|
||||
|
@ -106,14 +106,14 @@ public abstract class AbstractFridaCommand<T> implements FridaCommand<T> {
|
|||
res = keyValue.get("value");
|
||||
String key = element.getAsString();
|
||||
if (!key.equals(name)) {
|
||||
manager.getEventListeners().fire.consoleOutput(res+"\n", 0);
|
||||
manager.getEventListeners().invoke().consoleOutput(res+"\n", 0);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
manager.getEventListeners().fire.consoleOutput(object+"\n", 0);
|
||||
manager.getEventListeners().invoke().consoleOutput(object+"\n", 0);
|
||||
}
|
||||
} else {
|
||||
manager.getEventListeners().fire.consoleOutput(object+"\n", 0);
|
||||
manager.getEventListeners().invoke().consoleOutput(object+"\n", 0);
|
||||
}
|
||||
if ("[]".equals(res.toString())) {
|
||||
Msg.error(this, "nothing returned for "+this);
|
||||
|
|
|
@ -46,7 +46,7 @@ public class FridaDetachCommand extends AbstractFridaCommand<Void> {
|
|||
for (FridaThread thread : list) {
|
||||
manager.removeThread(pid, FridaClient.getId(thread));
|
||||
}
|
||||
manager.getEventListeners().fire.processRemoved(pid, FridaCause.Causes.UNCLAIMED);
|
||||
manager.getEventListeners().invoke().processRemoved(pid, FridaCause.Causes.UNCLAIMED);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
private final HandlerMap<FridaEvent<?>, Void, DebugStatus> handlerMap = new HandlerMap<>();
|
||||
private final Map<Class<?>, DebugStatus> statusMap = new LinkedHashMap<>();
|
||||
private final ListenerSet<FridaEventsListener> listenersEvent =
|
||||
new ListenerSet<>(FridaEventsListener.class);
|
||||
new ListenerSet<>(FridaEventsListener.class, true);
|
||||
|
||||
private FridaTarget currentTarget;
|
||||
private FridaSession currentSession;
|
||||
|
@ -157,7 +157,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
for (String tid : toRemove) {
|
||||
removeThread(processId, tid);
|
||||
}
|
||||
getEventListeners().fire.processRemoved(id, cause);
|
||||
getEventListeners().invoke().processRemoved(id, cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,7 +213,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
if (sessions.remove(id) == null) {
|
||||
throw new IllegalArgumentException("There is no session with id " + id);
|
||||
}
|
||||
getEventListeners().fire.sessionRemoved(id, cause);
|
||||
getEventListeners().invoke().sessionRemoved(id, cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -672,8 +672,8 @@ public class FridaManagerImpl implements FridaManager {
|
|||
protected DebugStatus processThreadCreated(FridaThreadCreatedEvent evt, Void v) {
|
||||
FridaThread thread = evt.getInfo().thread;
|
||||
currentThread = thread;
|
||||
getEventListeners().fire.threadCreated(thread, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
|
||||
getEventListeners().invoke().threadCreated(thread, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
|
@ -686,8 +686,8 @@ public class FridaManagerImpl implements FridaManager {
|
|||
*/
|
||||
protected DebugStatus processThreadReplaced(FridaThreadReplacedEvent evt, Void v) {
|
||||
FridaThread thread = evt.getInfo().thread;
|
||||
getEventListeners().fire.threadReplaced(thread, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
|
||||
getEventListeners().invoke().threadReplaced(thread, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
|
@ -699,7 +699,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processThreadExited(FridaThreadExitedEvent evt, Void v) {
|
||||
getEventListeners().fire.threadExited(currentThread, currentProcess, evt.getCause());
|
||||
getEventListeners().invoke().threadExited(currentThread, currentProcess, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
|
@ -712,7 +712,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
*/
|
||||
protected DebugStatus processThreadSelected(FridaThreadSelectedEvent evt, Void v) {
|
||||
currentThread = evt.getThread();
|
||||
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
|
@ -725,7 +725,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
*/
|
||||
protected DebugStatus processFrameSelected(FridaSelectedFrameChangedEvent evt, Void v) {
|
||||
currentThread = evt.getThread();
|
||||
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
|
@ -739,8 +739,8 @@ public class FridaManagerImpl implements FridaManager {
|
|||
protected DebugStatus processProcessCreated(FridaProcessCreatedEvent evt, Void v) {
|
||||
FridaProcessInfo info = evt.getInfo();
|
||||
FridaProcess proc = info.process;
|
||||
getEventListeners().fire.processAdded(proc, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.processSelected(proc, evt.getCause());
|
||||
getEventListeners().invoke().processAdded(proc, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().processSelected(proc, evt.getCause());
|
||||
|
||||
/*
|
||||
FridaThread thread = proc.GetSelectedThread();
|
||||
|
@ -759,8 +759,8 @@ public class FridaManagerImpl implements FridaManager {
|
|||
protected DebugStatus processProcessReplaced(FridaProcessReplacedEvent evt, Void v) {
|
||||
FridaProcessInfo info = evt.getInfo();
|
||||
FridaProcess proc = info.process;
|
||||
getEventListeners().fire.processReplaced(proc, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.processSelected(proc, evt.getCause());
|
||||
getEventListeners().invoke().processReplaced(proc, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().processSelected(proc, evt.getCause());
|
||||
|
||||
/*
|
||||
FridaThread thread = proc.GetSelectedThread();
|
||||
|
@ -779,9 +779,9 @@ public class FridaManagerImpl implements FridaManager {
|
|||
protected DebugStatus processProcessExited(FridaProcessExitedEvent evt, Void v) {
|
||||
FridaThread thread = getCurrentThread();
|
||||
FridaProcess process = getCurrentProcess();
|
||||
getEventListeners().fire.threadExited(thread, process, evt.getCause());
|
||||
getEventListeners().fire.processExited(process, evt.getCause());
|
||||
getEventListeners().fire.processRemoved(process.getPID().toString(), evt.getCause());
|
||||
getEventListeners().invoke().threadExited(thread, process, evt.getCause());
|
||||
getEventListeners().invoke().processExited(process, evt.getCause());
|
||||
getEventListeners().invoke().processRemoved(process.getPID().toString(), evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
|
@ -794,7 +794,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
*/
|
||||
protected DebugStatus processProcessSelected(FridaProcessSelectedEvent evt, Void v) {
|
||||
currentProcess = evt.getProcess();
|
||||
getEventListeners().fire.processSelected(currentProcess, evt.getCause());
|
||||
getEventListeners().invoke().processSelected(currentProcess, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
|
@ -807,8 +807,8 @@ public class FridaManagerImpl implements FridaManager {
|
|||
*/
|
||||
protected DebugStatus processSessionCreated(FridaSessionCreatedEvent evt, Void v) {
|
||||
FridaSessionInfo info = evt.getInfo();
|
||||
getEventListeners().fire.sessionAdded(info.session, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.sessionSelected(info.session, evt.getCause());
|
||||
getEventListeners().invoke().sessionAdded(info.session, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().sessionSelected(info.session, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
|
@ -821,8 +821,8 @@ public class FridaManagerImpl implements FridaManager {
|
|||
*/
|
||||
protected DebugStatus processSessionReplaced(FridaSessionReplacedEvent evt, Void v) {
|
||||
FridaSessionInfo info = evt.getInfo();
|
||||
getEventListeners().fire.sessionReplaced(info.session, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.sessionSelected(info.session, evt.getCause());
|
||||
getEventListeners().invoke().sessionReplaced(info.session, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().sessionSelected(info.session, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
|
@ -835,10 +835,10 @@ public class FridaManagerImpl implements FridaManager {
|
|||
*/
|
||||
protected DebugStatus processSessionExited(FridaSessionExitedEvent evt, Void v) {
|
||||
removeSession(evt.sessionId, FridaCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.sessionRemoved(evt.sessionId, evt.getCause());
|
||||
getEventListeners().fire.threadExited(currentThread, currentProcess, evt.getCause());
|
||||
getEventListeners().fire.processExited(currentProcess, evt.getCause());
|
||||
getEventListeners().fire.processRemoved(currentProcess.getPID().toString(),
|
||||
getEventListeners().invoke().sessionRemoved(evt.sessionId, evt.getCause());
|
||||
getEventListeners().invoke().threadExited(currentThread, currentProcess, evt.getCause());
|
||||
getEventListeners().invoke().processExited(currentProcess, evt.getCause());
|
||||
getEventListeners().invoke().processRemoved(currentProcess.getPID().toString(),
|
||||
evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
@ -855,7 +855,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
long n = info.getNumberOfModules();
|
||||
FridaProcess process = info.getProcess();
|
||||
for (int i = 0; i < n; i++) {
|
||||
getEventListeners().fire.moduleLoaded(process, info, i, evt.getCause());
|
||||
getEventListeners().invoke().moduleLoaded(process, info, i, evt.getCause());
|
||||
}
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
@ -872,7 +872,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
long n = info.getNumberOfModules();
|
||||
FridaProcess process = info.getProcess();
|
||||
for (int i = 0; i < n; i++) {
|
||||
getEventListeners().fire.moduleReplaced(process, info, i, evt.getCause());
|
||||
getEventListeners().invoke().moduleReplaced(process, info, i, evt.getCause());
|
||||
}
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
@ -889,7 +889,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
long n = info.getNumberOfModules();
|
||||
FridaProcess process = info.getProcess();
|
||||
for (int i = 0; i < n; i++) {
|
||||
getEventListeners().fire.moduleUnloaded(process, info, i, evt.getCause());
|
||||
getEventListeners().invoke().moduleUnloaded(process, info, i, evt.getCause());
|
||||
}
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
@ -906,7 +906,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
long n = info.getNumberOfRegions();
|
||||
FridaProcess process = info.getProcess();
|
||||
for (int i = 0; i < n; i++) {
|
||||
getEventListeners().fire.regionAdded(process, info, i, evt.getCause());
|
||||
getEventListeners().invoke().regionAdded(process, info, i, evt.getCause());
|
||||
}
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
@ -923,7 +923,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
long n = info.getNumberOfRegions();
|
||||
FridaProcess process = info.getProcess();
|
||||
for (int i = 0; i < n; i++) {
|
||||
getEventListeners().fire.regionReplaced(process, info, i, evt.getCause());
|
||||
getEventListeners().invoke().regionReplaced(process, info, i, evt.getCause());
|
||||
}
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
@ -940,7 +940,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
long n = info.getNumberOfRegions();
|
||||
FridaProcess process = info.getProcess();
|
||||
for (int i = 0; i < n; i++) {
|
||||
getEventListeners().fire.regionRemoved(process, info, i, evt.getCause());
|
||||
getEventListeners().invoke().regionRemoved(process, info, i, evt.getCause());
|
||||
}
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
@ -1000,13 +1000,13 @@ public class FridaManagerImpl implements FridaManager {
|
|||
*/
|
||||
protected DebugStatus processSessionSelected(FridaSessionSelectedEvent evt, Void v) {
|
||||
FridaSession session = evt.getSession();
|
||||
getEventListeners().fire.sessionSelected(session, evt.getCause());
|
||||
getEventListeners().invoke().sessionSelected(session, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
protected void processConsoleOutput(FridaConsoleOutputEvent evt, Void v) {
|
||||
if (evt.getOutput() != null) {
|
||||
getEventListeners().fire.consoleOutput(evt.getOutput(), evt.getMask());
|
||||
getEventListeners().invoke().consoleOutput(evt.getOutput(), evt.getMask());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1195,7 +1195,7 @@ public class FridaManagerImpl implements FridaManager {
|
|||
public CompletableFuture<Void> console(String command) {
|
||||
if (continuation != null) {
|
||||
String prompt = command.equals("") ? FridaModelTargetInterpreter.FRIDA_PROMPT : ">>>";
|
||||
getEventListeners().fire.promptChanged(prompt);
|
||||
getEventListeners().invoke().promptChanged(prompt);
|
||||
continuation.complete(command);
|
||||
setContinuation(null);
|
||||
return AsyncUtils.nil();
|
||||
|
|
|
@ -98,7 +98,7 @@ public class FridaModelImpl extends AbstractFridaModel implements DebuggerObject
|
|||
|
||||
@Override
|
||||
public void terminate() throws IOException {
|
||||
listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL);
|
||||
listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL);
|
||||
root.invalidateSubtree(root, "Frida is terminating");
|
||||
manager.terminate();
|
||||
}
|
||||
|
|
|
@ -239,11 +239,11 @@ public class GdbManagerImpl implements GdbManager {
|
|||
Collections.unmodifiableMap(breakpoints);
|
||||
|
||||
protected final ListenerSet<GdbEventsListener> listenersEvent =
|
||||
new ListenerSet<>(GdbEventsListener.class);
|
||||
new ListenerSet<>(GdbEventsListener.class, true);
|
||||
protected final ListenerSet<GdbTargetOutputListener> listenersTargetOutput =
|
||||
new ListenerSet<>(GdbTargetOutputListener.class);
|
||||
new ListenerSet<>(GdbTargetOutputListener.class, true);
|
||||
protected final ListenerSet<GdbConsoleOutputListener> listenersConsoleOutput =
|
||||
new ListenerSet<>(GdbConsoleOutputListener.class);
|
||||
new ListenerSet<>(GdbConsoleOutputListener.class, true);
|
||||
protected final ExecutorService eventThread = Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
|
@ -401,7 +401,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
|
||||
@Internal // for detach command
|
||||
public void fireThreadExited(int tid, GdbInferiorImpl inferior, GdbCause cause) {
|
||||
event(() -> listenersEvent.fire.threadExited(tid, inferior, cause), "threadExited");
|
||||
event(() -> listenersEvent.invoke().threadExited(tid, inferior, cause), "threadExited");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -474,7 +474,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
throw new IllegalArgumentException("There is already inferior " + exists);
|
||||
}
|
||||
inferiors.put(inferior.getId(), inferior);
|
||||
event(() -> listenersEvent.fire.inferiorAdded(inferior, cause), "addInferior");
|
||||
event(() -> listenersEvent.invoke().inferiorAdded(inferior, cause), "addInferior");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -488,7 +488,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
if (inferiors.remove(iid) == null) {
|
||||
throw new IllegalArgumentException("There is no inferior with id " + iid);
|
||||
}
|
||||
event(() -> listenersEvent.fire.inferiorRemoved(iid, cause), "removeInferior");
|
||||
event(() -> listenersEvent.invoke().inferiorRemoved(iid, cause), "removeInferior");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -505,7 +505,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
if (curInferior != inf) {
|
||||
curInferior = inf;
|
||||
if (fire) {
|
||||
event(() -> listenersEvent.fire.inferiorSelected(inf, cause),
|
||||
event(() -> listenersEvent.invoke().inferiorSelected(inf, cause),
|
||||
"updateCurrentInferior");
|
||||
}
|
||||
return true;
|
||||
|
@ -1080,7 +1080,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
String out = evt.getOutput();
|
||||
//System.out.print(out);
|
||||
if (!evt.isStolen()) {
|
||||
listenersConsoleOutput.fire.output(Channel.STDOUT, out);
|
||||
listenersConsoleOutput.invoke().output(Channel.STDOUT, out);
|
||||
}
|
||||
if (evt.getInterpreter() == Interpreter.MI2 &&
|
||||
out.toLowerCase().contains("switching to inferior")) {
|
||||
|
@ -1097,7 +1097,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
* @param v nothing
|
||||
*/
|
||||
protected void processTargetOut(GdbTargetOutputEvent evt, Void v) {
|
||||
listenersTargetOutput.fire.output(evt.getOutput());
|
||||
listenersTargetOutput.invoke().output(evt.getOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1110,7 +1110,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
String out = evt.getOutput();
|
||||
//System.err.print(out);
|
||||
if (!evt.isStolen()) {
|
||||
listenersConsoleOutput.fire.output(Channel.STDERR, out);
|
||||
listenersConsoleOutput.invoke().output(Channel.STDERR, out);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1134,7 +1134,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
}
|
||||
inferior.add(evt.getCause());
|
||||
if (fireSelected) {
|
||||
event(() -> listenersEvent.fire.inferiorSelected(inferior, evt.getCause()),
|
||||
event(() -> listenersEvent.invoke().inferiorSelected(inferior, evt.getCause()),
|
||||
"groupAdded-sel");
|
||||
}
|
||||
}
|
||||
|
@ -1161,7 +1161,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
}
|
||||
inferior.remove(evt.getCause());
|
||||
if (fireSelected) {
|
||||
event(() -> listenersEvent.fire.inferiorSelected(cur, evt.getCause()),
|
||||
event(() -> listenersEvent.invoke().inferiorSelected(cur, evt.getCause()),
|
||||
"groupRemoved-sel");
|
||||
// Also cause GDB to generate thread selection events, if applicable
|
||||
setActiveInferior(cur, false);
|
||||
|
@ -1182,7 +1182,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
}
|
||||
|
||||
public void fireInferiorStarted(GdbInferiorImpl inf, GdbCause cause, String text) {
|
||||
event(() -> listenersEvent.fire.inferiorStarted(inf, cause), text);
|
||||
event(() -> listenersEvent.invoke().inferiorStarted(inf, cause), text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1195,7 +1195,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
int iid = evt.getInferiorId();
|
||||
GdbInferiorImpl inf = getInferior(iid);
|
||||
inf.setExitCode(evt.getExitCode());
|
||||
event(() -> listenersEvent.fire.inferiorExited(inf, evt.getCause()), "inferiorExited");
|
||||
event(() -> listenersEvent.invoke().inferiorExited(inf, evt.getCause()), "inferiorExited");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1210,7 +1210,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
GdbInferiorImpl inf = getInferior(iid);
|
||||
GdbThreadImpl thread = new GdbThreadImpl(this, inf, tid);
|
||||
thread.add();
|
||||
event(() -> listenersEvent.fire.threadCreated(thread, evt.getCause()), "threadCreated");
|
||||
event(() -> listenersEvent.invoke().threadCreated(thread, evt.getCause()), "threadCreated");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1225,7 +1225,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
GdbInferiorImpl inf = getInferior(iid);
|
||||
GdbThreadImpl thread = inf.getThread(tid);
|
||||
thread.remove();
|
||||
event(() -> listenersEvent.fire.threadExited(tid, inf, evt.getCause()), "threadExited");
|
||||
event(() -> listenersEvent.invoke().threadExited(tid, inf, evt.getCause()), "threadExited");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1250,7 +1250,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
*/
|
||||
public void doThreadSelected(GdbThreadImpl thread, GdbStackFrame frame, GdbCause cause) {
|
||||
updateCurrentInferior(thread.getInferior(), cause, true);
|
||||
event(() -> listenersEvent.fire.threadSelected(thread, frame, cause), "threadSelected");
|
||||
event(() -> listenersEvent.invoke().threadSelected(thread, frame, cause), "threadSelected");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1265,14 +1265,14 @@ public class GdbManagerImpl implements GdbManager {
|
|||
if (iid == null) { // Context of all inferiors
|
||||
for (GdbInferiorImpl inf : inferiors.values()) {
|
||||
inf.libraryLoaded(name);
|
||||
event(() -> listenersEvent.fire.libraryLoaded(inf, name, evt.getCause()),
|
||||
event(() -> listenersEvent.invoke().libraryLoaded(inf, name, evt.getCause()),
|
||||
"libraryLoaded");
|
||||
}
|
||||
}
|
||||
else {
|
||||
GdbInferiorImpl inf = getInferior(iid);
|
||||
inf.libraryLoaded(name);
|
||||
event(() -> listenersEvent.fire.libraryLoaded(inf, name, evt.getCause()),
|
||||
event(() -> listenersEvent.invoke().libraryLoaded(inf, name, evt.getCause()),
|
||||
"libraryLoaded");
|
||||
}
|
||||
}
|
||||
|
@ -1289,14 +1289,14 @@ public class GdbManagerImpl implements GdbManager {
|
|||
if (iid == null) { // Context of all inferiors
|
||||
for (GdbInferiorImpl inf : inferiors.values()) {
|
||||
inf.libraryUnloaded(name);
|
||||
event(() -> listenersEvent.fire.libraryUnloaded(inf, name, evt.getCause()),
|
||||
event(() -> listenersEvent.invoke().libraryUnloaded(inf, name, evt.getCause()),
|
||||
"libraryUnloaded");
|
||||
}
|
||||
}
|
||||
else {
|
||||
GdbInferiorImpl inf = getInferior(iid);
|
||||
inf.libraryUnloaded(name);
|
||||
event(() -> listenersEvent.fire.libraryUnloaded(inf, name, evt.getCause()),
|
||||
event(() -> listenersEvent.invoke().libraryUnloaded(inf, name, evt.getCause()),
|
||||
"libraryUnloaded");
|
||||
}
|
||||
}
|
||||
|
@ -1310,7 +1310,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
@Internal
|
||||
public void doBreakpointCreated(GdbBreakpointInfo newInfo, GdbCause cause) {
|
||||
addKnownBreakpoint(newInfo, false);
|
||||
event(() -> listenersEvent.fire.breakpointCreated(newInfo, cause), "breakpointCreated");
|
||||
event(() -> listenersEvent.invoke().breakpointCreated(newInfo, cause), "breakpointCreated");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1332,7 +1332,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
@Internal
|
||||
public void doBreakpointModified(GdbBreakpointInfo newInfo, GdbCause cause) {
|
||||
GdbBreakpointInfo oldInfo = addKnownBreakpoint(newInfo, true);
|
||||
event(() -> listenersEvent.fire.breakpointModified(newInfo, oldInfo, cause),
|
||||
event(() -> listenersEvent.invoke().breakpointModified(newInfo, oldInfo, cause),
|
||||
"breakpointModified");
|
||||
}
|
||||
|
||||
|
@ -1358,7 +1358,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
if (oldInfo == null) {
|
||||
return;
|
||||
}
|
||||
event(() -> listenersEvent.fire.breakpointDeleted(oldInfo, cause), "breakpointDeleted");
|
||||
event(() -> listenersEvent.invoke().breakpointDeleted(oldInfo, cause), "breakpointDeleted");
|
||||
}
|
||||
|
||||
protected void doBreakpointModifiedSameLocations(GdbBreakpointInfo newInfo,
|
||||
|
@ -1367,7 +1367,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
return;
|
||||
}
|
||||
addKnownBreakpoint(newInfo, true);
|
||||
event(() -> listenersEvent.fire.breakpointModified(newInfo, oldInfo, cause),
|
||||
event(() -> listenersEvent.invoke().breakpointModified(newInfo, oldInfo, cause),
|
||||
"breakpointModified");
|
||||
}
|
||||
|
||||
|
@ -1412,7 +1412,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
protected void processMemoryChanged(GdbMemoryChangedEvent evt, Void v) {
|
||||
int iid = evt.getInferiorId();
|
||||
GdbInferior inf = getInferior(iid);
|
||||
event(() -> listenersEvent.fire.memoryChanged(inf, evt.getAddress(), evt.getLength(),
|
||||
event(() -> listenersEvent.invoke().memoryChanged(inf, evt.getAddress(), evt.getLength(),
|
||||
evt.getCause()), "memoryChanged");
|
||||
}
|
||||
|
||||
|
@ -1423,7 +1423,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
* @param v nothing
|
||||
*/
|
||||
protected void processParamChanged(GdbParamChangedEvent evt, Void v) {
|
||||
event(() -> listenersEvent.fire.paramChanged(evt.getParam(), evt.getValue(),
|
||||
event(() -> listenersEvent.invoke().paramChanged(evt.getParam(), evt.getValue(),
|
||||
evt.getCause()), "paramChanged");
|
||||
}
|
||||
|
||||
|
@ -1467,7 +1467,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
GdbMiFieldList newFrame = evt.checkFrame();
|
||||
GdbStackFrameImpl frame =
|
||||
newFrame == null ? null : GdbStackFrameImpl.fromFieldList(thread, newFrame);
|
||||
event(() -> listenersEvent.fire.threadSelected(thread, frame, evt), "command-done");
|
||||
event(() -> listenersEvent.invoke().threadSelected(thread, frame, evt), "command-done");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1538,7 +1538,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
if ("all".equals(threadId)) {
|
||||
GdbInferiorImpl cur = curInferior;
|
||||
event(() -> {
|
||||
listenersEvent.fire.inferiorStateChanged(cur, cur.getKnownThreads().values(),
|
||||
listenersEvent.invoke().inferiorStateChanged(cur, cur.getKnownThreads().values(),
|
||||
evt.newState(), null, evt.getCause(), evt.getReason());
|
||||
}, "inferiorState-running");
|
||||
for (GdbThreadImpl thread : curInferior.getKnownThreadsImpl().values()) {
|
||||
|
@ -1549,7 +1549,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
int id = Integer.parseUnsignedInt(threadId);
|
||||
GdbThreadImpl thread = threads.get(id);
|
||||
event(() -> {
|
||||
listenersEvent.fire.inferiorStateChanged(thread.getInferior(),
|
||||
listenersEvent.invoke().inferiorStateChanged(thread.getInferior(),
|
||||
List.of(thread), evt.newState(), null, evt.getCause(), evt.getReason());
|
||||
}, "inferiorState-running");
|
||||
thread.setState(evt.newState(), evt.getCause(), evt.getReason());
|
||||
|
@ -1584,13 +1584,13 @@ public class GdbManagerImpl implements GdbManager {
|
|||
}
|
||||
for (Map.Entry<GdbInferior, Set<GdbThread>> ent : byInf.entrySet()) {
|
||||
event(() -> {
|
||||
listenersEvent.fire.inferiorStateChanged(ent.getKey(), ent.getValue(),
|
||||
listenersEvent.invoke().inferiorStateChanged(ent.getKey(), ent.getValue(),
|
||||
evt.newState(), evtThread, evt.getCause(), evt.getReason());
|
||||
}, "inferiorState-stopped");
|
||||
}
|
||||
if (evtThread != null) {
|
||||
GdbStackFrameImpl frame = evt.getFrame(evtThread);
|
||||
event(() -> listenersEvent.fire.threadSelected(evtThread, frame, evt),
|
||||
event(() -> listenersEvent.invoke().threadSelected(evtThread, frame, evt),
|
||||
"inferiorState-stopped");
|
||||
}
|
||||
}
|
||||
|
@ -1701,7 +1701,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
|
||||
@Internal
|
||||
public void synthesizeConsoleOut(Channel channel, String line) {
|
||||
listenersConsoleOutput.fire.output(channel, line);
|
||||
listenersConsoleOutput.invoke().output(channel, line);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -81,7 +81,7 @@ public class GdbThreadImpl implements GdbThread {
|
|||
this.inferior.addThread(this);
|
||||
this.manager.addThread(this);
|
||||
state.addChangeListener((oldState, newState, pair) -> {
|
||||
manager.event(() -> manager.listenersEvent.fire.threadStateChanged(this, newState,
|
||||
manager.event(() -> manager.listenersEvent.invoke().threadStateChanged(this, newState,
|
||||
pair.cause, pair.reason), "threadState");
|
||||
});
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel {
|
|||
}
|
||||
|
||||
public void terminate() throws IOException {
|
||||
listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL);
|
||||
listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL);
|
||||
session.invalidateSubtree(session, "GDB is terminating");
|
||||
gdb.terminate();
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
*/
|
||||
package agent.gdb.model.impl;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -30,15 +31,11 @@ import ghidra.dbg.target.schema.TargetAttributeType;
|
|||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "BreakpointSpec",
|
||||
attributes = {
|
||||
@TargetAttributeType(type = Void.class) },
|
||||
canonicalContainer = true)
|
||||
@TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = {
|
||||
@TargetAttributeType(type = Void.class) }, canonicalContainer = true)
|
||||
public class GdbModelTargetBreakpointSpec extends
|
||||
DefaultTargetObject<GdbModelTargetBreakpointLocation, GdbModelTargetBreakpointContainer>
|
||||
implements TargetBreakpointSpec, TargetDeletable {
|
||||
|
@ -62,12 +59,7 @@ public class GdbModelTargetBreakpointSpec extends
|
|||
protected final Map<Long, GdbModelTargetBreakpointLocation> breaksBySub =
|
||||
new WeakValueHashMap<>();
|
||||
protected final ListenerSet<TargetBreakpointAction> actions =
|
||||
new ListenerSet<>(TargetBreakpointAction.class) {
|
||||
// Use strong references on actions
|
||||
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
|
||||
return new LinkedHashMap<>();
|
||||
};
|
||||
};
|
||||
new ListenerSet<>(TargetBreakpointAction.class, false);
|
||||
|
||||
public GdbModelTargetBreakpointSpec(GdbModelTargetBreakpointContainer breakpoints,
|
||||
GdbBreakpointInfo info) {
|
||||
|
@ -171,13 +163,12 @@ public class GdbModelTargetBreakpointSpec extends
|
|||
}
|
||||
|
||||
protected void updateAttributesFromInfo(String reason) {
|
||||
changeAttributes(List.of(), Map.of(
|
||||
ENABLED_ATTRIBUTE_NAME, enabled = info.isEnabled(),
|
||||
EXPRESSION_ATTRIBUTE_NAME,
|
||||
expression = info.getType() == GdbBreakpointType.CATCHPOINT ? info.getCatchType()
|
||||
: info.getOriginalLocation(),
|
||||
KINDS_ATTRIBUTE_NAME, kinds = computeKinds(info),
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay()),
|
||||
changeAttributes(List.of(),
|
||||
Map.of(ENABLED_ATTRIBUTE_NAME, enabled = info.isEnabled(), EXPRESSION_ATTRIBUTE_NAME,
|
||||
expression = info.getType() == GdbBreakpointType.CATCHPOINT ? info.getCatchType()
|
||||
: info.getOriginalLocation(),
|
||||
KINDS_ATTRIBUTE_NAME, kinds = computeKinds(info), DISPLAY_ATTRIBUTE_NAME,
|
||||
display = computeDisplay()),
|
||||
reason);
|
||||
}
|
||||
|
||||
|
@ -232,7 +223,7 @@ public class GdbModelTargetBreakpointSpec extends
|
|||
|
||||
protected void breakpointHit(GdbModelTargetStackFrame frame,
|
||||
GdbModelTargetBreakpointLocation eb) {
|
||||
actions.fire.breakpointHit(this, frame.thread, frame, eb);
|
||||
actions.invoke().breakpointHit(this, frame.thread, frame, eb);
|
||||
}
|
||||
|
||||
public synchronized GdbModelTargetBreakpointLocation getTargetBreakpointLocation(
|
||||
|
@ -270,15 +261,21 @@ public class GdbModelTargetBreakpointSpec extends
|
|||
case BREAKPOINT:
|
||||
case HW_BREAKPOINT:
|
||||
case OTHER:
|
||||
return String.format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(),
|
||||
info.getDisp(), enb, addr, what).trim();
|
||||
return String
|
||||
.format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(),
|
||||
info.getDisp(), enb, addr, what)
|
||||
.trim();
|
||||
case CATCHPOINT:
|
||||
return String.format("%d %s %s %s %s", info.getNumber(), info.getTypeName(),
|
||||
info.getDisp(), enb, what).trim();
|
||||
return String
|
||||
.format("%d %s %s %s %s", info.getNumber(), info.getTypeName(),
|
||||
info.getDisp(), enb, what)
|
||||
.trim();
|
||||
case DPRINTF:
|
||||
// TODO: script?
|
||||
return String.format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(),
|
||||
info.getDisp(), enb, addr, what).trim();
|
||||
return String
|
||||
.format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(),
|
||||
info.getDisp(), enb, addr, what)
|
||||
.trim();
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public class LldbDetachCommand extends AbstractLldbCommand<Void> {
|
|||
SBThread t = process.GetThreadAtIndex(i);
|
||||
manager.removeThread(pid, DebugClient.getId(t));
|
||||
}
|
||||
manager.getEventListeners().fire.processRemoved(pid, LldbCause.Causes.UNCLAIMED);
|
||||
manager.getEventListeners().invoke().processRemoved(pid, LldbCause.Causes.UNCLAIMED);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
private final HandlerMap<LldbEvent<?>, Void, DebugStatus> handlerMap = new HandlerMap<>();
|
||||
private final Map<Class<?>, DebugStatus> statusMap = new LinkedHashMap<>();
|
||||
private final ListenerSet<LldbEventsListener> listenersEvent =
|
||||
new ListenerSet<>(LldbEventsListener.class);
|
||||
new ListenerSet<>(LldbEventsListener.class, true);
|
||||
|
||||
private SBEvent currentEvent;
|
||||
private SBTarget currentSession;
|
||||
|
@ -116,7 +116,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Use {@link SBThreadImpl#remove()} instead
|
||||
*
|
||||
*
|
||||
* @param id the thread ID to remove
|
||||
*/
|
||||
public void removeThread(String processId, String id) {
|
||||
|
@ -136,8 +136,9 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
public void addThreadIfAbsent(SBProcess process, SBThread thread) {
|
||||
synchronized (threads) {
|
||||
if (!process.IsValid())
|
||||
if (!process.IsValid()) {
|
||||
return;
|
||||
}
|
||||
Map<String, SBThread> map = threads.get(DebugClient.getId(process));
|
||||
if (map == null) {
|
||||
map = new HashMap<>();
|
||||
|
@ -165,7 +166,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Use {@link SBProcessImpl#remove(LldbCause)} instead
|
||||
*
|
||||
*
|
||||
* @param id the process ID to remove
|
||||
* @param cause the cause of removal
|
||||
*/
|
||||
|
@ -188,18 +189,10 @@ public class LldbManagerImpl implements LldbManager {
|
|||
for (String tid : toRemove) {
|
||||
removeThread(processId, tid);
|
||||
}
|
||||
getEventListeners().fire.processRemoved(id, cause);
|
||||
getEventListeners().invoke().processRemoved(id, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the selected process
|
||||
*
|
||||
* @param process the process that now has focus
|
||||
* @param cause the cause of the focus change
|
||||
* @param fire signal listeners
|
||||
* @return success status
|
||||
*/
|
||||
@Override
|
||||
public SBProcess getProcess(SBTarget session, String id) {
|
||||
synchronized (processes) {
|
||||
|
@ -214,8 +207,9 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
public void addProcessIfAbsent(SBTarget session, SBProcess process) {
|
||||
synchronized (processes) {
|
||||
if (!session.IsValid())
|
||||
if (!session.IsValid()) {
|
||||
return;
|
||||
}
|
||||
String sessionId = DebugClient.getId(session);
|
||||
Map<String, SBProcess> map = processes.get(sessionId);
|
||||
if (map == null) {
|
||||
|
@ -242,7 +236,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Use {@link SBTargetImpl#remove(LldbCause)} instead
|
||||
*
|
||||
*
|
||||
* @param id the session ID to remove
|
||||
* @param cause the cause of removal
|
||||
*/
|
||||
|
@ -252,7 +246,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
if (sessions.remove(id) == null) {
|
||||
throw new IllegalArgumentException("There is no session with id " + id);
|
||||
}
|
||||
getEventListeners().fire.sessionRemoved(id, cause);
|
||||
getEventListeners().invoke().sessionRemoved(id, cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,7 +284,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Use {@link SBModule#remove()} instead
|
||||
*
|
||||
*
|
||||
* @param id the module name to remove
|
||||
*/
|
||||
public void removeModule(SBTarget session, String id) {
|
||||
|
@ -310,8 +304,9 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
public void addModuleIfAbsent(SBTarget session, SBModule module) {
|
||||
synchronized (modules) {
|
||||
if (!session.IsValid())
|
||||
if (!session.IsValid()) {
|
||||
return;
|
||||
}
|
||||
String sessionId = DebugClient.getId(session);
|
||||
Map<String, SBModule> map = modules.get(sessionId);
|
||||
if (map == null) {
|
||||
|
@ -347,8 +342,9 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
public void addBreakpointIfAbsent(SBTarget session, Object bpt) {
|
||||
synchronized (breakpoints) {
|
||||
if (!session.IsValid())
|
||||
if (!session.IsValid()) {
|
||||
return;
|
||||
}
|
||||
String sessionId = DebugClient.getId(session);
|
||||
Map<String, Object> map = breakpoints.get(sessionId);
|
||||
if (map == null) {
|
||||
|
@ -484,13 +480,11 @@ public class LldbManagerImpl implements LldbManager {
|
|||
state.set(null, Causes.UNCLAIMED);
|
||||
boolean create = true;
|
||||
if (args.length == 0) {
|
||||
executor =
|
||||
new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
|
||||
executor = new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
|
||||
}
|
||||
else {
|
||||
// TODO - process args
|
||||
executor =
|
||||
new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
|
||||
executor = new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
|
||||
create = false;
|
||||
}
|
||||
executor.setManager(this);
|
||||
|
@ -540,7 +534,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Schedule a command for execution
|
||||
*
|
||||
*
|
||||
* @param cmd the command to execute
|
||||
* @return the pending command, which acts as a future for later completion
|
||||
*/
|
||||
|
@ -741,16 +735,14 @@ public class LldbManagerImpl implements LldbManager {
|
|||
@Override
|
||||
public void updateState(SBProcess process) {
|
||||
currentProcess = eventProcess = process;
|
||||
if (currentSession == null ||
|
||||
!currentSession.IsValid() ||
|
||||
if (currentSession == null || !currentSession.IsValid() ||
|
||||
!currentSession.equals(process.GetTarget())) {
|
||||
SBTarget candidateSession = currentProcess.GetTarget();
|
||||
if (candidateSession != null && candidateSession.IsValid()) {
|
||||
currentSession = eventSession = candidateSession;
|
||||
}
|
||||
}
|
||||
if (currentThread == null ||
|
||||
!currentThread.IsValid() ||
|
||||
if (currentThread == null || !currentThread.IsValid() ||
|
||||
!currentThread.equals(process.GetSelectedThread())) {
|
||||
SBThread candidateThread = currentProcess.GetSelectedThread();
|
||||
if (candidateThread != null && candidateThread.IsValid()) {
|
||||
|
@ -764,7 +756,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Default handler for events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -775,7 +767,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -785,7 +777,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
for (int i = 0; i < currentSession.GetNumBreakpoints(); i++) {
|
||||
SBBreakpoint bpt = currentSession.GetBreakpointAtIndex(i);
|
||||
if (bpt.IsValid() && (bpt.GetID() == id.intValue())) {
|
||||
getEventListeners().fire.breakpointHit(bpt, evt.getCause());
|
||||
getEventListeners().invoke().breakpointHit(bpt, evt.getCause());
|
||||
}
|
||||
}
|
||||
return statusMap.get(evt.getClass());
|
||||
|
@ -793,7 +785,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -801,7 +793,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
protected DebugStatus processException(LldbExceptionEvent evt, Void v) {
|
||||
/*
|
||||
Integer eventId = updateState(evt);
|
||||
|
||||
|
||||
DebugExceptionRecord64 info = evt.getInfo();
|
||||
String key = Integer.toHexString(info.code);
|
||||
if (statusByNameMap.containsKey(key)) {
|
||||
|
@ -813,7 +805,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -824,72 +816,72 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for thread created events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processThreadCreated(LldbThreadCreatedEvent evt, Void v) {
|
||||
SBThread thread = evt.getInfo().thread;
|
||||
getEventListeners().fire.threadCreated(thread, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
|
||||
getEventListeners().invoke().threadCreated(thread, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for thread created events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processThreadReplaced(LldbThreadReplacedEvent evt, Void v) {
|
||||
SBThread thread = evt.getInfo().thread;
|
||||
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for thread exited events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processThreadExited(LldbThreadExitedEvent evt, Void v) {
|
||||
getEventListeners().fire.threadExited(eventThread, eventProcess, evt.getCause());
|
||||
getEventListeners().invoke().threadExited(eventThread, eventProcess, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for thread selected events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processThreadSelected(LldbThreadSelectedEvent evt, Void v) {
|
||||
currentThread = evt.getThread();
|
||||
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for frame selected events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processFrameSelected(LldbSelectedFrameChangedEvent evt, Void v) {
|
||||
currentThread = evt.getThread();
|
||||
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for process created events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -897,17 +889,17 @@ public class LldbManagerImpl implements LldbManager {
|
|||
protected DebugStatus processProcessCreated(LldbProcessCreatedEvent evt, Void v) {
|
||||
DebugProcessInfo info = evt.getInfo();
|
||||
SBProcess proc = info.process;
|
||||
getEventListeners().fire.processAdded(proc, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.processSelected(proc, evt.getCause());
|
||||
getEventListeners().invoke().processAdded(proc, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().processSelected(proc, evt.getCause());
|
||||
|
||||
SBThread thread = proc.GetSelectedThread();
|
||||
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for process replaced events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -915,17 +907,17 @@ public class LldbManagerImpl implements LldbManager {
|
|||
protected DebugStatus processProcessReplaced(LldbProcessReplacedEvent evt, Void v) {
|
||||
DebugProcessInfo info = evt.getInfo();
|
||||
SBProcess proc = info.process;
|
||||
getEventListeners().fire.processReplaced(proc, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.processSelected(proc, evt.getCause());
|
||||
getEventListeners().invoke().processReplaced(proc, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().processSelected(proc, evt.getCause());
|
||||
|
||||
SBThread thread = proc.GetSelectedThread();
|
||||
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
|
||||
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for process exited events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -933,73 +925,74 @@ public class LldbManagerImpl implements LldbManager {
|
|||
protected DebugStatus processProcessExited(LldbProcessExitedEvent evt, Void v) {
|
||||
SBThread thread = getCurrentThread();
|
||||
SBProcess process = getCurrentProcess();
|
||||
getEventListeners().fire.threadExited(thread, process, evt.getCause());
|
||||
getEventListeners().fire.processExited(process, evt.getCause());
|
||||
getEventListeners().fire.processRemoved(process.GetProcessID().toString(), evt.getCause());
|
||||
getEventListeners().invoke().threadExited(thread, process, evt.getCause());
|
||||
getEventListeners().invoke().processExited(process, evt.getCause());
|
||||
getEventListeners().invoke()
|
||||
.processRemoved(process.GetProcessID().toString(), evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for process selected events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processProcessSelected(LldbProcessSelectedEvent evt, Void v) {
|
||||
currentProcess = evt.getProcess();
|
||||
getEventListeners().fire.processSelected(currentProcess, evt.getCause());
|
||||
getEventListeners().invoke().processSelected(currentProcess, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for session created events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processSessionCreated(LldbSessionCreatedEvent evt, Void v) {
|
||||
DebugSessionInfo info = evt.getInfo();
|
||||
getEventListeners().fire.sessionAdded(info.session, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.sessionSelected(info.session, evt.getCause());
|
||||
getEventListeners().invoke().sessionAdded(info.session, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().sessionSelected(info.session, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for session replaced events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processSessionReplaced(LldbSessionReplacedEvent evt, Void v) {
|
||||
DebugSessionInfo info = evt.getInfo();
|
||||
getEventListeners().fire.sessionReplaced(info.session, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.sessionSelected(info.session, evt.getCause());
|
||||
getEventListeners().invoke().sessionReplaced(info.session, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().invoke().sessionSelected(info.session, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for session exited events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processSessionExited(LldbSessionExitedEvent evt, Void v) {
|
||||
removeSession(evt.sessionId, LldbCause.Causes.UNCLAIMED);
|
||||
getEventListeners().fire.sessionRemoved(evt.sessionId, evt.getCause());
|
||||
getEventListeners().fire.threadExited(eventThread, eventProcess, evt.getCause());
|
||||
getEventListeners().fire.processExited(eventProcess, evt.getCause());
|
||||
getEventListeners().fire.processRemoved(eventProcess.GetProcessID().toString(),
|
||||
evt.getCause());
|
||||
getEventListeners().invoke().sessionRemoved(evt.sessionId, evt.getCause());
|
||||
getEventListeners().invoke().threadExited(eventThread, eventProcess, evt.getCause());
|
||||
getEventListeners().invoke().processExited(eventProcess, evt.getCause());
|
||||
getEventListeners().invoke()
|
||||
.processRemoved(eventProcess.GetProcessID().toString(), evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for module loaded events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -1009,14 +1002,14 @@ public class LldbManagerImpl implements LldbManager {
|
|||
long n = info.getNumberOfModules();
|
||||
SBProcess process = info.getProcess();
|
||||
for (int i = 0; i < n; i++) {
|
||||
getEventListeners().fire.moduleLoaded(process, info, i, evt.getCause());
|
||||
getEventListeners().invoke().moduleLoaded(process, info, i, evt.getCause());
|
||||
}
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for module unloaded events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -1026,14 +1019,14 @@ public class LldbManagerImpl implements LldbManager {
|
|||
long n = info.getNumberOfModules();
|
||||
SBProcess process = info.getProcess();
|
||||
for (int i = 0; i < n; i++) {
|
||||
getEventListeners().fire.moduleUnloaded(process, info, i, evt.getCause());
|
||||
getEventListeners().invoke().moduleUnloaded(process, info, i, evt.getCause());
|
||||
}
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for state changed events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -1092,20 +1085,20 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for session selected events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
*/
|
||||
protected DebugStatus processSessionSelected(LldbSessionSelectedEvent evt, Void v) {
|
||||
SBTarget session = evt.getSession();
|
||||
getEventListeners().fire.sessionSelected(session, evt.getCause());
|
||||
getEventListeners().invoke().sessionSelected(session, evt.getCause());
|
||||
return statusMap.get(evt.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for systems events
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
* @return retval handling/break status
|
||||
|
@ -1116,13 +1109,13 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
protected void processConsoleOutput(LldbConsoleOutputEvent evt, Void v) {
|
||||
if (evt.getOutput() != null) {
|
||||
getEventListeners().fire.consoleOutput(evt.getOutput(), evt.getMask());
|
||||
getEventListeners().invoke().consoleOutput(evt.getOutput(), evt.getMask());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for breakpoint-created event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1134,7 +1127,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-modified event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1146,7 +1139,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-deleted event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1157,7 +1150,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-enable event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1168,7 +1161,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-deleted event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1179,7 +1172,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-invalidated event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1191,7 +1184,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-modified event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1204,12 +1197,11 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-locations added event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
protected void processBreakpointLocationsAdded(LldbBreakpointLocationsAddedEvent evt,
|
||||
Void v) {
|
||||
protected void processBreakpointLocationsAdded(LldbBreakpointLocationsAddedEvent evt, Void v) {
|
||||
SBTarget session = getCurrentSession();
|
||||
Object info = evt.getBreakpointInfo();
|
||||
doBreakpointModified(session, info, evt.getCause());
|
||||
|
@ -1217,7 +1209,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-locations removed event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1230,7 +1222,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-modified event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1243,7 +1235,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-modified event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1255,7 +1247,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-modified event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1268,7 +1260,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-modified event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1280,7 +1272,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-modified event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1292,7 +1284,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Handler for breakpoint-modified event
|
||||
*
|
||||
*
|
||||
* @param evt the event
|
||||
* @param v nothing
|
||||
*/
|
||||
|
@ -1304,31 +1296,31 @@ public class LldbManagerImpl implements LldbManager {
|
|||
|
||||
/**
|
||||
* Fire breakpoint created event
|
||||
*
|
||||
*
|
||||
* @param newInfo the new information
|
||||
* @param cause the cause of the creation
|
||||
*/
|
||||
@Internal
|
||||
public void doBreakpointCreated(SBTarget session, Object info, LldbCause cause) {
|
||||
addKnownBreakpoint(session, info, false);
|
||||
getEventListeners().fire.breakpointCreated(info, cause);
|
||||
getEventListeners().invoke().breakpointCreated(info, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire breakpoint modified event
|
||||
*
|
||||
*
|
||||
* @param newInfo the new information
|
||||
* @param cause the cause of the modification
|
||||
*/
|
||||
@Internal
|
||||
public void doBreakpointModified(SBTarget session, Object info, LldbCause cause) {
|
||||
addKnownBreakpoint(session, info, true);
|
||||
getEventListeners().fire.breakpointModified(info, cause);
|
||||
getEventListeners().invoke().breakpointModified(info, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire breakpoint deleted event
|
||||
*
|
||||
*
|
||||
* @param number the deleted breakpoint number
|
||||
* @param cause the cause of the deletion
|
||||
*/
|
||||
|
@ -1338,13 +1330,13 @@ public class LldbManagerImpl implements LldbManager {
|
|||
if (oldInfo == null) {
|
||||
return;
|
||||
}
|
||||
getEventListeners().fire.breakpointDeleted(oldInfo, cause);
|
||||
getEventListeners().invoke().breakpointDeleted(oldInfo, cause);
|
||||
}
|
||||
|
||||
protected void doBreakpointModifiedSameLocations(SBTarget session, Object info,
|
||||
LldbCause cause) {
|
||||
addKnownBreakpoint(session, info, true);
|
||||
getEventListeners().fire.breakpointModified(info, cause);
|
||||
getEventListeners().invoke().breakpointModified(info, cause);
|
||||
}
|
||||
|
||||
@Internal
|
||||
|
@ -1568,16 +1560,16 @@ public class LldbManagerImpl implements LldbManager {
|
|||
Msg.warn(this, "defaulting to active thread");
|
||||
return currentThread;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < currentProcess.GetNumThreads(); i++) {
|
||||
SBThread thread = currentProcess.GetThreadAtIndex(i);
|
||||
if (thread.IsValid()) {
|
||||
Msg.warn(this, "defaulting to thread "+i);
|
||||
Msg.warn(this, "defaulting to thread " + i);
|
||||
currentThread = thread;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return currentThread;
|
||||
}
|
||||
|
||||
|
@ -1638,7 +1630,7 @@ public class LldbManagerImpl implements LldbManager {
|
|||
public CompletableFuture<Void> console(String command) {
|
||||
if (continuation != null) {
|
||||
String prompt = command.equals("") ? LldbModelTargetInterpreter.LLDB_PROMPT : ">>>";
|
||||
getEventListeners().fire.promptChanged(prompt);
|
||||
getEventListeners().invoke().promptChanged(prompt);
|
||||
continuation.complete(command);
|
||||
setContinuation(null);
|
||||
return AsyncUtils.nil();
|
||||
|
|
|
@ -88,7 +88,7 @@ public interface LldbModelTargetBreakpointSpec extends //
|
|||
public default void breakpointHit() {
|
||||
LldbModelTargetThread targetThread =
|
||||
getParentProcess().getThreads().getTargetThread(getManager().getEventThread());
|
||||
getActions().fire.breakpointHit((LldbModelTargetBreakpointSpec) getProxy(), targetThread,
|
||||
getActions().invoke().breakpointHit((LldbModelTargetBreakpointSpec) getProxy(), targetThread,
|
||||
null, findLocation(targetThread));
|
||||
}
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ public class LldbModelImpl extends AbstractLldbModel implements DebuggerObjectMo
|
|||
|
||||
@Override
|
||||
public void terminate() throws IOException {
|
||||
listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL);
|
||||
listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL);
|
||||
root.invalidateSubtree(root, "LLDB is terminating");
|
||||
manager.terminate();
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
*/
|
||||
package agent.lldb.model.impl;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import SWIG.SBBreakpointLocation;
|
||||
|
@ -27,23 +28,16 @@ import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
|
|||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "BreakpointSpec",
|
||||
elements = { //
|
||||
@TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class)
|
||||
},
|
||||
attributes = {
|
||||
@TargetObjectSchemaInfo(name = "BreakpointSpec", elements = { //
|
||||
@TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class) }, attributes = {
|
||||
@TargetAttributeType(name = "Type", type = String.class),
|
||||
@TargetAttributeType(name = "Valid", type = Boolean.class),
|
||||
@TargetAttributeType(name = "Enabled", type = Boolean.class),
|
||||
@TargetAttributeType(name = "Count", type = Long.class),
|
||||
@TargetAttributeType(type = Void.class)
|
||||
},
|
||||
canonicalContainer = true)
|
||||
@TargetAttributeType(type = Void.class) }, canonicalContainer = true)
|
||||
public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetObjectImpl
|
||||
implements LldbModelTargetBreakpointSpec {
|
||||
|
||||
|
@ -60,12 +54,7 @@ public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetO
|
|||
protected final Map<Integer, LldbModelTargetBreakpointLocation> breaksBySub =
|
||||
new WeakValueHashMap<>();
|
||||
protected final ListenerSet<TargetBreakpointAction> actions =
|
||||
new ListenerSet<>(TargetBreakpointAction.class) {
|
||||
// Use strong references on actions
|
||||
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
|
||||
return new LinkedHashMap<>();
|
||||
};
|
||||
};
|
||||
new ListenerSet<>(TargetBreakpointAction.class, false);
|
||||
|
||||
public LldbModelTargetAbstractXpointSpec(LldbModelTargetBreakpointContainer breakpoints,
|
||||
Object info, String title) {
|
||||
|
@ -159,7 +148,7 @@ public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetO
|
|||
|
||||
protected void breakpointHit(LldbModelTargetStackFrame frame,
|
||||
LldbModelTargetBreakpointLocation eb) {
|
||||
actions.fire.breakpointHit(this, frame.getParentThread(), frame, eb);
|
||||
actions.invoke().breakpointHit(this, frame.getParentThread(), frame, eb);
|
||||
}
|
||||
|
||||
public synchronized LldbModelTargetBreakpointLocation getTargetBreakpointLocation(
|
||||
|
@ -180,7 +169,7 @@ public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetO
|
|||
|
||||
@Override
|
||||
public ListenerSet<TargetBreakpointAction> getActions() {
|
||||
return new ListenerSet<TargetBreakpointAction>(null);
|
||||
return new ListenerSet<TargetBreakpointAction>(null, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
package agent.lldb.model.impl;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import SWIG.SBBreakpoint;
|
||||
|
@ -27,31 +28,19 @@ import ghidra.dbg.target.TargetBreakpointLocation;
|
|||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "BreakpointSpec",
|
||||
elements = { //
|
||||
@TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class)
|
||||
},
|
||||
attributes = {
|
||||
@TargetObjectSchemaInfo(name = "BreakpointSpec", elements = { //
|
||||
@TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class) }, attributes = {
|
||||
@TargetAttributeType(name = "Type", type = String.class),
|
||||
@TargetAttributeType(name = "Valid", type = Boolean.class),
|
||||
@TargetAttributeType(name = "Enabled", type = Boolean.class),
|
||||
@TargetAttributeType(name = "Count", type = Long.class),
|
||||
@TargetAttributeType(type = Void.class)
|
||||
},
|
||||
canonicalContainer = true)
|
||||
@TargetAttributeType(type = Void.class) }, canonicalContainer = true)
|
||||
public class LldbModelTargetBreakpointSpecImpl extends LldbModelTargetAbstractXpointSpec {
|
||||
|
||||
protected final ListenerSet<TargetBreakpointAction> actions =
|
||||
new ListenerSet<>(TargetBreakpointAction.class) {
|
||||
// Use strong references on actions
|
||||
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
|
||||
return new LinkedHashMap<>();
|
||||
};
|
||||
};
|
||||
new ListenerSet<>(TargetBreakpointAction.class, false);
|
||||
|
||||
public LldbModelTargetBreakpointSpecImpl(LldbModelTargetBreakpointContainer breakpoints,
|
||||
Object info) {
|
||||
|
|
|
@ -34,7 +34,6 @@ import ghidra.dbg.target.TargetObject;
|
|||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import utilities.util.ProxyUtilities;
|
||||
|
||||
|
@ -60,8 +59,8 @@ public class DelegateGadpClientTargetObject
|
|||
for (Map.Entry<K, MethodHandle> ent : that.handles.entrySet()) {
|
||||
MethodHandle old = handles.put(ent.getKey(), ent.getValue());
|
||||
if (old != null) {
|
||||
throw new AssertionError("Conflict over handler for " + ent.getKey() +
|
||||
": " + old + " and " + ent.getValue());
|
||||
throw new AssertionError("Conflict over handler for " + ent.getKey() + ": " +
|
||||
old + " and " + ent.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,8 +160,8 @@ public class DelegateGadpClientTargetObject
|
|||
|
||||
Set<Class<? extends TargetObject>> allMixins = new HashSet<>(mixins);
|
||||
allMixins.add(GadpClientTargetObject.class);
|
||||
this.eventHandlers = EVENT_HANDLER_MAPS_BY_COMPOSITION.computeIfAbsent(allMixins,
|
||||
GadpEventHandlerMap::new);
|
||||
this.eventHandlers =
|
||||
EVENT_HANDLER_MAPS_BY_COMPOSITION.computeIfAbsent(allMixins, GadpEventHandlerMap::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -187,11 +186,14 @@ public class DelegateGadpClientTargetObject
|
|||
|
||||
@Override
|
||||
public CompletableFuture<Void> resync(RefreshBehavior attributes, RefreshBehavior elements) {
|
||||
return client.sendChecked(Gadp.ResyncRequest.newBuilder()
|
||||
.setPath(GadpValueUtils.makePath(path))
|
||||
.setAttributes(attributes.equals(RefreshBehavior.REFRESH_ALWAYS))
|
||||
.setElements(elements.equals(RefreshBehavior.REFRESH_ALWAYS)),
|
||||
Gadp.ResyncReply.getDefaultInstance()).thenApply(rep -> null);
|
||||
return client
|
||||
.sendChecked(
|
||||
Gadp.ResyncRequest.newBuilder()
|
||||
.setPath(GadpValueUtils.makePath(path))
|
||||
.setAttributes(attributes.equals(RefreshBehavior.REFRESH_ALWAYS))
|
||||
.setElements(elements.equals(RefreshBehavior.REFRESH_ALWAYS)),
|
||||
Gadp.ResyncReply.getDefaultInstance())
|
||||
.thenApply(rep -> null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -238,9 +240,11 @@ public class DelegateGadpClientTargetObject
|
|||
public synchronized CompletableFuture<Void> invalidateCaches() {
|
||||
assertValid();
|
||||
doClearCaches();
|
||||
return client.sendChecked(Gadp.CacheInvalidateRequest.newBuilder()
|
||||
.setPath(GadpValueUtils.makePath(path)),
|
||||
Gadp.CacheInvalidateReply.getDefaultInstance()).thenApply(rep -> null);
|
||||
return client
|
||||
.sendChecked(
|
||||
Gadp.CacheInvalidateRequest.newBuilder().setPath(GadpValueUtils.makePath(path)),
|
||||
Gadp.CacheInvalidateReply.getDefaultInstance())
|
||||
.thenApply(rep -> null);
|
||||
}
|
||||
|
||||
protected synchronized CachedMemory getMemoryCache(AddressSpace space) {
|
||||
|
@ -276,12 +280,7 @@ public class DelegateGadpClientTargetObject
|
|||
|
||||
protected synchronized ListenerSet<TargetBreakpointAction> getActions(boolean createIfAbsent) {
|
||||
if (actions == null && createIfAbsent) {
|
||||
actions = new ListenerSet<>(TargetBreakpointAction.class) {
|
||||
// Want strong references on actions
|
||||
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
|
||||
return new LinkedHashMap<>();
|
||||
};
|
||||
};
|
||||
actions = new ListenerSet<>(TargetBreakpointAction.class, false);
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
|
|
@ -323,10 +323,10 @@ public class GadpClient extends AbstractDebuggerObjectModel
|
|||
protected void channelStateChanged(ChannelState old, ChannelState set,
|
||||
DebuggerModelClosedReason reason) {
|
||||
if (old == ChannelState.NEGOTIATING && set == ChannelState.ACTIVE) {
|
||||
listeners.fire.modelOpened();
|
||||
listeners.invoke().modelOpened();
|
||||
}
|
||||
else if (old == ChannelState.ACTIVE && set == ChannelState.CLOSED) {
|
||||
listeners.fire.modelClosed(reason);
|
||||
listeners.invoke().modelClosed(reason);
|
||||
root.invalidateSubtree(root, "GADP Client disconnected");
|
||||
messageMatcher.flush(new DebuggerModelTerminatingException("GADP Client disconnected"));
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ public interface GadpClientTargetBreakpointSpecContainer
|
|||
GadpClientTargetBreakpointSpec specObj = (GadpClientTargetBreakpointSpec) spec;
|
||||
ListenerSet<TargetBreakpointAction> actions = specObj.getDelegate().getActions(false);
|
||||
if (actions != null) {
|
||||
actions.fire.breakpointHit(specObj, trapped, frame, breakpoint);
|
||||
actions.invoke().breakpointHit(specObj, trapped, frame, breakpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public class JdiEventHandler implements Runnable {
|
|||
protected final AsyncReference<Integer, JdiCause> state =
|
||||
new AsyncReference<>(ThreadReference.THREAD_STATUS_NOT_STARTED);
|
||||
public final ListenerSet<JdiEventsListener> listenersEvent =
|
||||
new ListenerSet<>(JdiEventsListener.class);
|
||||
new ListenerSet<>(JdiEventsListener.class, true);
|
||||
protected final ExecutorService eventThread = Executors.newSingleThreadExecutor();
|
||||
|
||||
public JdiEventHandler() {
|
||||
|
@ -116,7 +116,7 @@ public class JdiEventHandler implements Runnable {
|
|||
else if (eventSet.suspendPolicy() == EventRequest.SUSPEND_ALL) {
|
||||
setCurrentThread(eventSet);
|
||||
event(
|
||||
() -> listenersEvent.fire.processStop(eventSet, JdiCause.Causes.UNCLAIMED),
|
||||
() -> listenersEvent.invoke().processStop(eventSet, JdiCause.Causes.UNCLAIMED),
|
||||
"processStopped");
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ public class JdiEventHandler implements Runnable {
|
|||
/*
|
||||
* Inform jdb command line processor that jdb is being shutdown. JDK-8154144.
|
||||
*/
|
||||
event(() -> listenersEvent.fire.processShutdown(event, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().processShutdown(event, JdiCause.Causes.UNCLAIMED),
|
||||
"processStopped");
|
||||
return null; ///false;
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processBreakpoint(BreakpointEvent evt) {
|
||||
event(() -> listenersEvent.fire.breakpointHit(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().breakpointHit(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"breakpointHit");
|
||||
return DebugStatus.BREAK;
|
||||
}
|
||||
|
@ -314,7 +314,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processException(ExceptionEvent evt) {
|
||||
event(() -> listenersEvent.fire.exceptionHit(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().exceptionHit(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"exceptionHit");
|
||||
return DebugStatus.BREAK;
|
||||
}
|
||||
|
@ -327,7 +327,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processMethodEntry(MethodEntryEvent evt) {
|
||||
event(() -> listenersEvent.fire.methodEntry(evt, JdiCause.Causes.UNCLAIMED), "methodEntry");
|
||||
event(() -> listenersEvent.invoke().methodEntry(evt, JdiCause.Causes.UNCLAIMED), "methodEntry");
|
||||
return DebugStatus.GO;
|
||||
}
|
||||
|
||||
|
@ -339,7 +339,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processMethodExit(MethodExitEvent evt) {
|
||||
event(() -> listenersEvent.fire.methodExit(evt, JdiCause.Causes.UNCLAIMED), "methodExit");
|
||||
event(() -> listenersEvent.invoke().methodExit(evt, JdiCause.Causes.UNCLAIMED), "methodExit");
|
||||
return DebugStatus.GO;
|
||||
}
|
||||
|
||||
|
@ -351,7 +351,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processClassPrepare(ClassPrepareEvent evt) {
|
||||
event(() -> listenersEvent.fire.classPrepare(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().classPrepare(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"classPrepare");
|
||||
/*
|
||||
if (!Env.specList.resolve(cle)) {
|
||||
|
@ -372,7 +372,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processClassUnload(ClassUnloadEvent evt) {
|
||||
event(() -> listenersEvent.fire.classUnload(evt, JdiCause.Causes.UNCLAIMED), "classUnload");
|
||||
event(() -> listenersEvent.invoke().classUnload(evt, JdiCause.Causes.UNCLAIMED), "classUnload");
|
||||
return DebugStatus.GO;
|
||||
}
|
||||
|
||||
|
@ -384,7 +384,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processMCEntered(MonitorContendedEnteredEvent evt) {
|
||||
event(() -> listenersEvent.fire.monitorContendedEntered(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().monitorContendedEntered(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"monitorContendedEntered");
|
||||
return DebugStatus.GO;
|
||||
}
|
||||
|
@ -397,7 +397,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processMCEnter(MonitorContendedEnterEvent evt) {
|
||||
event(() -> listenersEvent.fire.monitorContendedEnter(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().monitorContendedEnter(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"monitorContendedEnter");
|
||||
return DebugStatus.GO;
|
||||
}
|
||||
|
@ -410,7 +410,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processMonitorWaited(MonitorWaitedEvent evt) {
|
||||
event(() -> listenersEvent.fire.monitorWaited(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().monitorWaited(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"monitorWaited");
|
||||
return DebugStatus.GO;
|
||||
}
|
||||
|
@ -423,7 +423,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processMonitorWait(MonitorWaitEvent evt) {
|
||||
event(() -> listenersEvent.fire.monitorWait(evt, JdiCause.Causes.UNCLAIMED), "monitorWait");
|
||||
event(() -> listenersEvent.invoke().monitorWait(evt, JdiCause.Causes.UNCLAIMED), "monitorWait");
|
||||
return DebugStatus.GO;
|
||||
}
|
||||
|
||||
|
@ -436,7 +436,7 @@ public class JdiEventHandler implements Runnable {
|
|||
*/
|
||||
protected DebugStatus processStep(StepEvent evt) {
|
||||
evt.request().disable();
|
||||
event(() -> listenersEvent.fire.stepComplete(evt, JdiCause.Causes.UNCLAIMED), "step");
|
||||
event(() -> listenersEvent.invoke().stepComplete(evt, JdiCause.Causes.UNCLAIMED), "step");
|
||||
return DebugStatus.STEP_INTO;
|
||||
}
|
||||
|
||||
|
@ -448,7 +448,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processWatchpoint(WatchpointEvent evt) {
|
||||
event(() -> listenersEvent.fire.watchpointHit(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().watchpointHit(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"watchpointHit");
|
||||
return DebugStatus.BREAK;
|
||||
}
|
||||
|
@ -461,7 +461,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processAccessWatchpoint(AccessWatchpointEvent evt) {
|
||||
event(() -> listenersEvent.fire.accessWatchpointHit(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().accessWatchpointHit(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"accessWatchpointHit");
|
||||
return DebugStatus.BREAK;
|
||||
}
|
||||
|
@ -474,7 +474,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processWatchpointModification(ModificationWatchpointEvent evt) {
|
||||
event(() -> listenersEvent.fire.watchpointModified(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().watchpointModified(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"watchpointModified");
|
||||
return DebugStatus.GO;
|
||||
}
|
||||
|
@ -487,7 +487,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processThreadDeath(ThreadDeathEvent evt) {
|
||||
event(() -> listenersEvent.fire.threadExited(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().threadExited(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"threadExited");
|
||||
JdiThreadInfo.removeThread(evt.thread());
|
||||
return DebugStatus.GO;
|
||||
|
@ -502,7 +502,7 @@ public class JdiEventHandler implements Runnable {
|
|||
*/
|
||||
protected DebugStatus processThreadStart(ThreadStartEvent evt) {
|
||||
JdiThreadInfo.addThread(evt.thread());
|
||||
event(() -> listenersEvent.fire.threadStarted(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().threadStarted(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"threadStarted");
|
||||
return DebugStatus.GO;
|
||||
}
|
||||
|
@ -516,7 +516,7 @@ public class JdiEventHandler implements Runnable {
|
|||
*/
|
||||
protected DebugStatus processVMDeath(VMDeathEvent evt) {
|
||||
shutdownMessageKey = "The application exited";
|
||||
event(() -> listenersEvent.fire.vmDied(evt, JdiCause.Causes.UNCLAIMED), "vmDied");
|
||||
event(() -> listenersEvent.invoke().vmDied(evt, JdiCause.Causes.UNCLAIMED), "vmDied");
|
||||
return DebugStatus.BREAK;
|
||||
}
|
||||
|
||||
|
@ -529,7 +529,7 @@ public class JdiEventHandler implements Runnable {
|
|||
*/
|
||||
protected DebugStatus processVMDisconnect(VMDisconnectEvent evt) {
|
||||
shutdownMessageKey = "The application has been disconnected";
|
||||
event(() -> listenersEvent.fire.vmDisconnected(evt, JdiCause.Causes.UNCLAIMED),
|
||||
event(() -> listenersEvent.invoke().vmDisconnected(evt, JdiCause.Causes.UNCLAIMED),
|
||||
"vmDisconnected");
|
||||
return DebugStatus.BREAK;
|
||||
}
|
||||
|
@ -542,7 +542,7 @@ public class JdiEventHandler implements Runnable {
|
|||
* @return
|
||||
*/
|
||||
protected DebugStatus processVMStart(VMStartEvent evt) {
|
||||
event(() -> listenersEvent.fire.vmStarted(evt, JdiCause.Causes.UNCLAIMED), "vmStarted");
|
||||
event(() -> listenersEvent.invoke().vmStarted(evt, JdiCause.Causes.UNCLAIMED), "vmStarted");
|
||||
return DebugStatus.BREAK;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,9 +41,9 @@ public class JdiManagerImpl implements JdiManager {
|
|||
private final Map<String, VirtualMachine> unmodifiableVMs = Collections.unmodifiableMap(vms);
|
||||
|
||||
protected final ListenerSet<JdiTargetOutputListener> listenersTargetOutput =
|
||||
new ListenerSet<>(JdiTargetOutputListener.class);
|
||||
new ListenerSet<>(JdiTargetOutputListener.class, true);
|
||||
protected final ListenerSet<JdiConsoleOutputListener> listenersConsoleOutput =
|
||||
new ListenerSet<>(JdiConsoleOutputListener.class);
|
||||
new ListenerSet<>(JdiConsoleOutputListener.class, true);
|
||||
protected final ExecutorService eventThread = Executors.newSingleThreadExecutor();
|
||||
|
||||
protected JdiEventHandler globalEventHandler = new JdiEventHandler();
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
*/
|
||||
package ghidra.dbg.jdi.model;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
|
||||
|
@ -26,21 +27,12 @@ import ghidra.dbg.target.TargetBreakpointSpec;
|
|||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.schema.TargetAttributeType;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
|
||||
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "BreakpointSpec",
|
||||
attributes = {
|
||||
@TargetAttributeType(
|
||||
name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME,
|
||||
type = JdiModelTargetBreakpointContainer.class),
|
||||
@TargetAttributeType(
|
||||
name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME,
|
||||
type = JdiModelTargetBreakpointSpec.class),
|
||||
@TargetAttributeType(type = Void.class)
|
||||
},
|
||||
canonicalContainer = true)
|
||||
@TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = {
|
||||
@TargetAttributeType(name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, type = JdiModelTargetBreakpointContainer.class),
|
||||
@TargetAttributeType(name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, type = JdiModelTargetBreakpointSpec.class),
|
||||
@TargetAttributeType(type = Void.class) }, canonicalContainer = true)
|
||||
public class JdiModelTargetBreakpointSpec extends JdiModelTargetObjectImpl
|
||||
implements TargetBreakpointSpec, JdiModelTargetDeletable {
|
||||
|
||||
|
@ -48,12 +40,7 @@ public class JdiModelTargetBreakpointSpec extends JdiModelTargetObjectImpl
|
|||
protected TargetBreakpointKindSet kinds;
|
||||
|
||||
protected final ListenerSet<TargetBreakpointAction> actions =
|
||||
new ListenerSet<>(TargetBreakpointAction.class) {
|
||||
// Use strong references on actions
|
||||
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
};
|
||||
new ListenerSet<>(TargetBreakpointAction.class, false);
|
||||
|
||||
public JdiModelTargetBreakpointSpec(JdiModelTargetBreakpointContainer breakpoints,
|
||||
JdiBreakpointInfo info, boolean isElement) {
|
||||
|
|
|
@ -274,7 +274,7 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen
|
|||
}
|
||||
targetVM.vmStateChanged(targetState, reason);
|
||||
JdiEventHandler eventHandler = getManager().getEventHandler(targetVM.vm);
|
||||
eventHandler.listenersEvent.fire.threadStateChanged(thread, state,
|
||||
eventHandler.listenersEvent.invoke().threadStateChanged(thread, state,
|
||||
JdiCause.Causes.UNCLAIMED, reason);
|
||||
}
|
||||
|
||||
|
|
|
@ -217,7 +217,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
|||
trackingLabel.setText("");
|
||||
trackingLabel.setToolTipText("");
|
||||
trackingLabel.setForeground(Colors.FOREGROUND);
|
||||
trackingSpecChangeListeners.fire.locationTrackingSpecChanged(spec);
|
||||
trackingSpecChangeListeners.invoke().locationTrackingSpecChanged(spec);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -291,7 +291,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
|||
protected final ForListingReadsMemoryTrait readsMemTrait;
|
||||
|
||||
protected final ListenerSet<LocationTrackingSpecChangeListener> trackingSpecChangeListeners =
|
||||
new ListenerSet<>(LocationTrackingSpecChangeListener.class);
|
||||
new ListenerSet<>(LocationTrackingSpecChangeListener.class, true);
|
||||
|
||||
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
|
||||
protected final JLabel trackingLabel = new JLabel();
|
||||
|
|
|
@ -51,7 +51,7 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
|||
protected boolean showHidden = false;
|
||||
|
||||
private final ListenerSet<CellActivationListener> cellActivationListeners =
|
||||
new ListenerSet<>(CellActivationListener.class);
|
||||
new ListenerSet<>(CellActivationListener.class, true);
|
||||
|
||||
public AbstractQueryTablePanel(Plugin plugin) {
|
||||
super(new BorderLayout());
|
||||
|
@ -236,6 +236,6 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
|||
}
|
||||
|
||||
protected void fireCellActivated() {
|
||||
cellActivationListeners.fire.cellActivated(table);
|
||||
cellActivationListeners.invoke().cellActivated(table);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -815,7 +815,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
|||
|
||||
private final Object lock = new Object();
|
||||
private final ListenerSet<LogicalBreakpointsChangeListener> changeListeners =
|
||||
new ListenerSet<>(LogicalBreakpointsChangeListener.class);
|
||||
new ListenerSet<>(LogicalBreakpointsChangeListener.class, true);
|
||||
|
||||
private final TrackRecordersListener targetsListener = new TrackRecordersListener();
|
||||
private final TrackMappingsListener mappingListener = new TrackMappingsListener();
|
||||
|
@ -835,7 +835,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
|||
protected void processChange(Consumer<ChangeCollector> processor, String description) {
|
||||
executor.submit(() -> {
|
||||
// Invoke change callbacks without the lock! (try must surround sync)
|
||||
try (ChangeCollector c = new ChangeCollector(changeListeners.fire)) {
|
||||
try (ChangeCollector c = new ChangeCollector(changeListeners.invoke())) {
|
||||
synchronized (lock) {
|
||||
processor.accept(c);
|
||||
}
|
||||
|
|
|
@ -213,7 +213,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
|
|||
private final Map<Trace, ControlMode> currentModes = new HashMap<>();
|
||||
|
||||
private final ListenerSet<ControlModeChangeListener> listeners =
|
||||
new ListenerSet<>(ControlModeChangeListener.class);
|
||||
new ListenerSet<>(ControlModeChangeListener.class, true);
|
||||
|
||||
@Override
|
||||
public ControlMode getCurrentMode(Trace trace) {
|
||||
|
@ -232,7 +232,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
|
|||
}
|
||||
}
|
||||
if (newMode != oldMode) {
|
||||
listeners.fire.modeChanged(trace, newMode);
|
||||
listeners.invoke().modeChanged(trace, newMode);
|
||||
tool.contextChanged(null);
|
||||
}
|
||||
}
|
||||
|
@ -280,7 +280,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
|
|||
}
|
||||
}
|
||||
if (newMode != oldMode) {
|
||||
listeners.fire.modeChanged(trace, newMode);
|
||||
listeners.invoke().modeChanged(trace, newMode);
|
||||
tool.contextChanged(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -295,7 +295,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
.forgetValues((key, l) -> true);
|
||||
protected final Map<CachedEmulator, Integer> busy = new LinkedHashMap<>();
|
||||
protected final ListenerSet<EmulatorStateListener> stateListeners =
|
||||
new ListenerSet<>(EmulatorStateListener.class);
|
||||
new ListenerSet<>(EmulatorStateListener.class, true);
|
||||
|
||||
class BusyEmu implements AutoCloseable {
|
||||
private final CachedEmulator ce;
|
||||
|
@ -314,7 +314,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
}
|
||||
if (fire) {
|
||||
stateListeners.fire.running(ce);
|
||||
stateListeners.invoke().running(ce);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,7 +332,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
}
|
||||
if (fire) {
|
||||
stateListeners.fire.stopped(ce);
|
||||
stateListeners.invoke().stopped(ce);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
models.remove(model);
|
||||
}
|
||||
model.removeModelListener(this);
|
||||
modelListeners.fire.elementRemoved(model);
|
||||
modelListeners.invoke().elementRemoved(model);
|
||||
if (currentModel == model) {
|
||||
activateModel(null);
|
||||
}
|
||||
|
@ -155,11 +155,11 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
protected final Map<TargetObject, TraceRecorder> recordersByTarget = new WeakHashMap<>();
|
||||
|
||||
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners =
|
||||
new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class));
|
||||
new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class), true);
|
||||
protected final ListenerSet<CollectionChangeListener<DebuggerObjectModel>> modelListeners =
|
||||
new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class));
|
||||
new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class), true);
|
||||
protected final ListenerSet<CollectionChangeListener<TraceRecorder>> recorderListeners =
|
||||
new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class));
|
||||
new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class), true);
|
||||
protected final ChangeListener classChangeListener = new ChangeListenerForFactoryInstances();
|
||||
protected final ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders();
|
||||
|
||||
|
@ -264,7 +264,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
"Invalidated before or during add to service");
|
||||
}
|
||||
}
|
||||
modelListeners.fire.elementAdded(model);
|
||||
modelListeners.invoke().elementAdded(model);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -276,7 +276,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
return false;
|
||||
}
|
||||
}
|
||||
modelListeners.fire.elementRemoved(model);
|
||||
modelListeners.invoke().elementRemoved(model);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -315,7 +315,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
});
|
||||
recordersByTarget.put(target, recorder);
|
||||
}
|
||||
recorderListeners.fire.elementAdded(recorder);
|
||||
recorderListeners.invoke().elementAdded(recorder);
|
||||
// NOTE: It's possible the recorder stopped recording before we installed the listener
|
||||
if (!recorder.isRecording()) {
|
||||
doRemoveRecorder(recorder);
|
||||
|
@ -385,7 +385,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
}
|
||||
old.removeListener(listenerOnRecorders);
|
||||
}
|
||||
recorderListeners.fire.elementRemoved(recorder);
|
||||
recorderListeners.invoke().elementRemoved(recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -423,7 +423,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
diff.removeAll(newFactories);
|
||||
for (DebuggerModelFactory factory : diff) {
|
||||
factories.remove(factory);
|
||||
factoryListeners.fire.elementRemoved(factory);
|
||||
factoryListeners.invoke().elementRemoved(factory);
|
||||
}
|
||||
|
||||
diff.clear();
|
||||
|
@ -431,7 +431,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
diff.removeAll(factories);
|
||||
for (DebuggerModelFactory factory : diff) {
|
||||
factories.add(factory);
|
||||
factoryListeners.fire.elementAdded(factory);
|
||||
factoryListeners.invoke().elementAdded(factory);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -525,7 +525,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
removed = recordersByTarget.remove(recorder.getTarget()) != null;
|
||||
}
|
||||
if (removed) {
|
||||
recorderListeners.fire.elementRemoved(recorder);
|
||||
recorderListeners.invoke().elementRemoved(recorder);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,23 +72,10 @@ import ghidra.util.datastruct.ListenerSet;
|
|||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger models manager service (proxy to front-end)",
|
||||
description = "Manage debug sessions, connections, and trace recording",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
eventsConsumed = {
|
||||
ProgramActivatedPluginEvent.class,
|
||||
ProgramClosedPluginEvent.class,
|
||||
},
|
||||
servicesRequired = {
|
||||
@PluginInfo(shortDescription = "Debugger models manager service (proxy to front-end)", description = "Manage debug sessions, connections, and trace recording", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, eventsConsumed = {
|
||||
ProgramActivatedPluginEvent.class, ProgramClosedPluginEvent.class, }, servicesRequired = {
|
||||
DebuggerTargetService.class,
|
||||
DebuggerTraceManagerService.class,
|
||||
},
|
||||
servicesProvided = {
|
||||
DebuggerModelService.class,
|
||||
})
|
||||
DebuggerTraceManagerService.class, }, servicesProvided = { DebuggerModelService.class, })
|
||||
public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||
implements DebuggerModelServiceInternal {
|
||||
|
||||
|
@ -158,17 +145,17 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
implements CollectionChangeListener<DebuggerModelFactory> {
|
||||
@Override
|
||||
public void elementAdded(DebuggerModelFactory element) {
|
||||
factoryListeners.fire.elementAdded(element);
|
||||
factoryListeners.invoke().elementAdded(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementRemoved(DebuggerModelFactory element) {
|
||||
factoryListeners.fire.elementRemoved(element);
|
||||
factoryListeners.invoke().elementRemoved(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementModified(DebuggerModelFactory element) {
|
||||
factoryListeners.fire.elementModified(element);
|
||||
factoryListeners.invoke().elementModified(element);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,7 +163,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
implements CollectionChangeListener<DebuggerObjectModel> {
|
||||
@Override
|
||||
public void elementAdded(DebuggerObjectModel element) {
|
||||
modelListeners.fire.elementAdded(element);
|
||||
modelListeners.invoke().elementAdded(element);
|
||||
if (currentModel == null) {
|
||||
activateModel(element);
|
||||
}
|
||||
|
@ -187,12 +174,12 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
if (currentModel == element) {
|
||||
activateModel(null);
|
||||
}
|
||||
modelListeners.fire.elementRemoved(element);
|
||||
modelListeners.invoke().elementRemoved(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementModified(DebuggerObjectModel element) {
|
||||
modelListeners.fire.elementModified(element);
|
||||
modelListeners.invoke().elementModified(element);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,7 +187,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
implements CollectionChangeListener<TraceRecorder> {
|
||||
@Override
|
||||
public void elementAdded(TraceRecorder element) {
|
||||
recorderListeners.fire.elementAdded(element);
|
||||
recorderListeners.invoke().elementAdded(element);
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
TraceRecorderTarget target = new TraceRecorderTarget(tool, element);
|
||||
targets.put(element, target);
|
||||
|
@ -210,15 +197,16 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
|
||||
@Override
|
||||
public void elementRemoved(TraceRecorder element) {
|
||||
recorderListeners.fire.elementRemoved(element);
|
||||
recorderListeners.invoke().elementRemoved(element);
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
targetService.withdrawTarget(Objects.requireNonNull(targets.get(element)));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementModified(TraceRecorder element) {
|
||||
recorderListeners.fire.elementModified(element);
|
||||
recorderListeners.invoke().elementModified(element);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,11 +237,11 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
DockingAction actionDisconnectAll;
|
||||
|
||||
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners =
|
||||
new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class));
|
||||
new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class), true);
|
||||
protected final ListenerSet<CollectionChangeListener<DebuggerObjectModel>> modelListeners =
|
||||
new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class));
|
||||
new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class), true);
|
||||
protected final ListenerSet<CollectionChangeListener<TraceRecorder>> recorderListeners =
|
||||
new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class));
|
||||
new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class), true);
|
||||
|
||||
protected final Map<TraceRecorder, TraceRecorderTarget> targets = new HashMap<>();
|
||||
|
||||
|
@ -278,8 +266,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
// Note, I have to give an enabledWhen, otherwise any context change re-enables it
|
||||
MultiStateActionBuilder<DebuggerProgramLaunchOffer> builderDebugProgram =
|
||||
DebugProgramAction.buttonBuilder(this, delegate);
|
||||
actionDebugProgram = builderDebugProgram
|
||||
.enabledWhen(ctx -> currentProgram != null)
|
||||
actionDebugProgram = builderDebugProgram.enabledWhen(ctx -> currentProgram != null)
|
||||
.onAction(this::debugProgramButtonActivated)
|
||||
.onActionStateChanged(this::debugProgramStateActivated)
|
||||
.addState(DUMMY_LAUNCH_STATE)
|
||||
|
@ -333,8 +320,8 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
protected void writeMostRecentLaunches(Program program, List<String> mrl) {
|
||||
ProgramUserData userData = program.getProgramUserData();
|
||||
try (Transaction tid = userData.openTransaction()) {
|
||||
StringPropertyMap prop = userData
|
||||
.getStringProperty(getName(), KEY_MOST_RECENT_LAUNCHES, true);
|
||||
StringPropertyMap prop =
|
||||
userData.getStringProperty(getName(), KEY_MOST_RECENT_LAUNCHES, true);
|
||||
Address min = program.getAddressFactory().getDefaultAddressSpace().getMinAddress();
|
||||
prop.add(min, mrl.stream().collect(Collectors.joining(";")));
|
||||
}
|
||||
|
@ -425,8 +412,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
List<DebuggerProgramLaunchOffer> offers = program == null ? List.of()
|
||||
: getProgramLaunchOffers(program).collect(Collectors.toList());
|
||||
List<ActionState<DebuggerProgramLaunchOffer>> states = offers.stream()
|
||||
.map(o -> new ActionState<>(o.getButtonTitle(),
|
||||
o.getIcon(), o))
|
||||
.map(o -> new ActionState<>(o.getButtonTitle(), o.getIcon(), o))
|
||||
.collect(Collectors.toList());
|
||||
if (!states.isEmpty()) {
|
||||
actionDebugProgram.setActionStates(states);
|
||||
|
|
|
@ -46,7 +46,7 @@ public class DefaultProcessRecorder implements ManagedProcessRecorder {
|
|||
|
||||
protected void processMemoryAccessibilityChanged(boolean old,
|
||||
boolean acc, Void __) {
|
||||
recorder.getListeners().fire.processMemoryAccessibilityChanged(recorder);
|
||||
recorder.getListeners().invoke().processMemoryAccessibilityChanged(recorder);
|
||||
}
|
||||
|
||||
public CompletableFuture<byte[]> readProcessMemory(Address start, int length) {
|
||||
|
|
|
@ -154,7 +154,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
|||
return AsyncUtils.nil();
|
||||
}
|
||||
return initRegMapper(descs).thenAccept(__ -> {
|
||||
recorder.getListeners().fire.registerBankMapped(recorder);
|
||||
recorder.getListeners().invoke().registerBankMapped(recorder);
|
||||
}).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not intialize register mapper", ex);
|
||||
return null;
|
||||
|
|
|
@ -52,7 +52,7 @@ public class DefaultTimeRecorder {
|
|||
RecorderPermanentTransaction tid) {
|
||||
if (tid != null) {
|
||||
doAdvanceSnap(description, eventThread);
|
||||
recorder.getListeners().fire.snapAdvanced(recorder, getSnap());
|
||||
recorder.getListeners().invoke().snapAdvanced(recorder, getSnap());
|
||||
return;
|
||||
}
|
||||
// NB. The also serves as the snap counter, so it must be on the service thread
|
||||
|
@ -60,6 +60,6 @@ public class DefaultTimeRecorder {
|
|||
RecorderPermanentTransaction.start(trace, description)) {
|
||||
doAdvanceSnap(description, eventThread);
|
||||
}
|
||||
recorder.getListeners().fire.snapAdvanced(recorder, getSnap());
|
||||
recorder.getListeners().invoke().snapAdvanced(recorder, getSnap());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -367,7 +367,7 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
|||
@Override
|
||||
public void stopRecording() {
|
||||
invalidate();
|
||||
getListeners().fire.recordingStopped(this);
|
||||
getListeners().invoke().recordingStopped(this);
|
||||
}
|
||||
|
||||
protected void invalidate() {
|
||||
|
|
|
@ -58,7 +58,7 @@ public class TraceObjectManager {
|
|||
//private AbstractRecorderRegisterSet threadRegisters;
|
||||
|
||||
private final ListenerSet<TraceRecorderListener> listeners =
|
||||
new ListenerSet<>(TraceRecorderListener.class);
|
||||
new ListenerSet<>(TraceRecorderListener.class, true);
|
||||
|
||||
protected final Set<TargetBreakpointLocation> breakpoints = new HashSet<>();
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
|||
protected final ListenerForRecord listenerForRecord;
|
||||
|
||||
protected final ListenerSet<TraceRecorderListener> listeners =
|
||||
new ListenerSet<>(TraceRecorderListener.class);
|
||||
new ListenerSet<>(TraceRecorderListener.class, true);
|
||||
|
||||
// TODO: I don't like this here. Should ask the model, not the recorder.
|
||||
protected TargetObject curFocus;
|
||||
|
@ -805,11 +805,11 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
|||
}
|
||||
|
||||
protected void fireSnapAdvanced(long key) {
|
||||
listeners.fire.snapAdvanced(this, key);
|
||||
listeners.invoke().snapAdvanced(this, key);
|
||||
}
|
||||
|
||||
protected void fireRecordingStopped() {
|
||||
listeners.fire.recordingStopped(this);
|
||||
listeners.invoke().recordingStopped(this);
|
||||
}
|
||||
|
||||
// TODO: Deprecate/remove the other callbacks: registerBankMapped, *accessibilityChanged
|
||||
|
|
|
@ -544,7 +544,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
|||
private final AsyncDebouncer<Void> changeDebouncer =
|
||||
new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100);
|
||||
private final ListenerSet<DebuggerStaticMappingChangeListener> changeListeners =
|
||||
new ListenerSet<>(DebuggerStaticMappingChangeListener.class);
|
||||
new ListenerSet<>(DebuggerStaticMappingChangeListener.class, true);
|
||||
private Set<Trace> affectedTraces = new HashSet<>();
|
||||
private Set<Program> affectedPrograms = new HashSet<>();
|
||||
|
||||
|
@ -576,7 +576,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
|||
affectedTraces = new HashSet<>();
|
||||
affectedPrograms = new HashSet<>();
|
||||
}
|
||||
changeListeners.fire.mappingsChanged(traces, programs);
|
||||
changeListeners.invoke().mappingsChanged(traces, programs);
|
||||
}
|
||||
|
||||
private void traceAffected(Trace trace) {
|
||||
|
|
|
@ -27,15 +27,8 @@ import ghidra.framework.plugintool.util.PluginStatus;
|
|||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger targets manager service",
|
||||
description = "Maintains a collection of published targets and notifies listeners of changes.",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
servicesProvided = {
|
||||
DebuggerTargetService.class,
|
||||
})
|
||||
@PluginInfo(shortDescription = "Debugger targets manager service", description = "Maintains a collection of published targets and notifies listeners of changes.", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, servicesProvided = {
|
||||
DebuggerTargetService.class, })
|
||||
public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTargetService {
|
||||
|
||||
public DebuggerTargetServicePlugin(PluginTool tool) {
|
||||
|
@ -44,7 +37,7 @@ public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTarge
|
|||
|
||||
private final Map<Trace, Target> targets = new HashMap<>();
|
||||
private final ListenerSet<TargetPublicationListener> listeners =
|
||||
new ListenerSet<>(TargetPublicationListener.class);
|
||||
new ListenerSet<>(TargetPublicationListener.class, true);
|
||||
|
||||
@Override
|
||||
public void publishTarget(Target target) {
|
||||
|
@ -53,7 +46,7 @@ public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTarge
|
|||
notify = targets.put(target.getTrace(), target) != target;
|
||||
}
|
||||
if (notify) {
|
||||
listeners.fire.targetPublished(target);
|
||||
listeners.invoke().targetPublished(target);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +57,7 @@ public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTarge
|
|||
notify = targets.remove(target.getTrace()) == target;
|
||||
}
|
||||
if (notify) {
|
||||
listeners.fire.targetWithdrawn(target);
|
||||
listeners.invoke().targetWithdrawn(target);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,6 @@ import javax.swing.tree.TreePath;
|
|||
|
||||
import org.junit.*;
|
||||
import org.junit.rules.TestName;
|
||||
import org.junit.rules.TestWatcher;
|
||||
import org.junit.runner.Description;
|
||||
|
||||
import db.Transaction;
|
||||
import docking.ActionContext;
|
||||
|
@ -81,7 +79,7 @@ import ghidra.trace.model.memory.TraceMemorySpace;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.datastruct.ListenerMap;
|
||||
import ghidra.util.datastruct.TestDataStructureErrorHandlerInstaller;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.ConsoleTaskMonitor;
|
||||
|
||||
|
@ -103,8 +101,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
}
|
||||
|
||||
@Override
|
||||
protected DebuggerRegisterMapper createRegisterMapper(
|
||||
TargetRegisterContainer registers) {
|
||||
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
|
||||
return new DefaultDebuggerRegisterMapper(cSpec, registers, true);
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +139,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
/**
|
||||
* Works like {@link #waitForValue(Supplier)}, except this caches {@link NoSuchElementException}
|
||||
* and tries again.
|
||||
*
|
||||
*
|
||||
* @param <T> the type of object to wait for
|
||||
* @param supplier the supplier of the object
|
||||
* @return the object
|
||||
|
@ -172,7 +169,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
|
||||
/**
|
||||
* This is so gross
|
||||
*
|
||||
*
|
||||
* @param lockable
|
||||
*/
|
||||
protected void waitForLock(DomainObject lockable) {
|
||||
|
@ -184,7 +181,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
|
||||
/**
|
||||
* Get an address in the trace's default space
|
||||
*
|
||||
*
|
||||
* @param trace the trace
|
||||
* @param offset the byte offset in the default space
|
||||
* @return the address
|
||||
|
@ -195,7 +192,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
|
||||
/**
|
||||
* Get an address in the program's default space
|
||||
*
|
||||
*
|
||||
* @param program the program
|
||||
* @param offset the byte offset in the default space
|
||||
* @return the address
|
||||
|
@ -206,7 +203,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
|
||||
/**
|
||||
* Get an address range in the trace's default space
|
||||
*
|
||||
*
|
||||
* @param program the program
|
||||
* @param min the min byte offset in the default space
|
||||
* @param max the max byte offset in the default space
|
||||
|
@ -350,10 +347,10 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
|
||||
/**
|
||||
* Find the sub menu item of the current selection by text
|
||||
*
|
||||
*
|
||||
* Note that if the desired item is at the same level as the currently selected item, this
|
||||
* method will not find it. It searches the sub menu of the currently selected item.
|
||||
*
|
||||
*
|
||||
* @param text the text
|
||||
* @return the found item
|
||||
* @throws NoSuchElementException if the desired item is not found
|
||||
|
@ -365,7 +362,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
|
||||
/**
|
||||
* Activate via mouse the sub menu item of the current selection by text
|
||||
*
|
||||
*
|
||||
* @param text the text on the item to click
|
||||
* @throws AWTException
|
||||
* @throws NoSuchElementException if no item with the given text is found
|
||||
|
@ -481,9 +478,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
protected static void performEnabledAction(ActionContextProvider provider,
|
||||
DockingActionIf action, boolean wait) {
|
||||
ActionContext context = waitForValue(() -> {
|
||||
ActionContext ctx = provider == null
|
||||
? new DefaultActionContext()
|
||||
: provider.getActionContext(null);
|
||||
ActionContext ctx =
|
||||
provider == null ? new DefaultActionContext() : provider.getActionContext(null);
|
||||
if (!action.isEnabledForContext(ctx)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -556,15 +552,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
|
||||
@Rule
|
||||
public TestName name = new TestName();
|
||||
@Rule
|
||||
public TestWatcher watcher = new TestWatcher() {
|
||||
@Override
|
||||
protected void succeeded(Description description) {
|
||||
if (description.isTest()) {
|
||||
ListenerMap.checkErr();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected final ConsoleTaskMonitor monitor = new ConsoleTaskMonitor();
|
||||
|
||||
protected void waitRecorder(TraceRecorder recorder) throws Throwable {
|
||||
|
@ -586,9 +574,16 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
waitForDomainObject(recorder.getTrace());
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
|
||||
// Note: we may decided to move this up to a framework-level base test class
|
||||
TestDataStructureErrorHandlerInstaller.installConcurrentExceptionErrorHandler();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ListenerMap.clearErr();
|
||||
|
||||
env = new TestEnv();
|
||||
tool = env.getTool();
|
||||
|
||||
|
@ -670,8 +665,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
populateTestModel();
|
||||
|
||||
TargetObject target = chooseTarget();
|
||||
TraceRecorder recorder = modelService.recordTarget(target,
|
||||
createTargetTraceMapper(target), ActionSource.AUTOMATIC);
|
||||
TraceRecorder recorder = modelService.recordTarget(target, createTargetTraceMapper(target),
|
||||
ActionSource.AUTOMATIC);
|
||||
|
||||
waitRecorder(recorder);
|
||||
return recorder;
|
||||
|
@ -682,9 +677,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
|
||||
protected void intoProject(DomainObject obj) {
|
||||
waitForDomainObject(obj);
|
||||
DomainFolder rootFolder = tool.getProject()
|
||||
.getProjectData()
|
||||
.getRootFolder();
|
||||
DomainFolder rootFolder = tool.getProject().getProjectData().getRootFolder();
|
||||
waitForCondition(() -> {
|
||||
try {
|
||||
rootFolder.createFile(obj.getName(), obj, monitor);
|
||||
|
@ -799,8 +792,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged);
|
||||
}
|
||||
|
||||
void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
byte[] oldValue, byte[] newValue) {
|
||||
void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range, byte[] oldValue,
|
||||
byte[] newValue) {
|
||||
if (space.getThread() != traceThread) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -36,8 +36,8 @@ import ghidra.dbg.target.*;
|
|||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.debug.api.action.ActionSource;
|
||||
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
|
||||
import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener;
|
||||
import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
|
||||
import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.debug.api.model.TraceRecorder;
|
||||
import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener;
|
||||
|
@ -53,7 +53,6 @@ import ghidra.trace.model.memory.TraceMemoryRegion;
|
|||
import ghidra.trace.model.modules.TraceStaticMapping;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.ListenerMap;
|
||||
|
||||
public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
protected static final long TIMEOUT_MILLIS =
|
||||
|
@ -61,14 +60,13 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
|
||||
/**
|
||||
* Tracks the current set of logical breakpoints.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Its assertions require perfection in the sequence of events: 1) No double-adds. 2) No
|
||||
* double-removes. 3) No extraneous updates. At the end of each test, the current set of
|
||||
* breakpoints in this listener should be verified against those reported by the service.
|
||||
*/
|
||||
protected class NoDuplicatesTrackingChangeListener
|
||||
implements LogicalBreakpointsChangeListener {
|
||||
protected class NoDuplicatesTrackingChangeListener implements LogicalBreakpointsChangeListener {
|
||||
private Set<LogicalBreakpoint> current = new HashSet<>();
|
||||
|
||||
@Override
|
||||
|
@ -152,8 +150,6 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
|
||||
@Before
|
||||
public void setUpBreakpointServiceTest() throws Throwable {
|
||||
ListenerMap.clearErr();
|
||||
|
||||
addPlugin(tool, DebuggerLogicalBreakpointServicePlugin.class);
|
||||
breakpointService = tool.getService(DebuggerLogicalBreakpointService.class);
|
||||
mappingService = tool.getService(DebuggerStaticMappingService.class);
|
||||
|
@ -197,7 +193,6 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
waitForLock(recorder3.getTrace());
|
||||
recorder3.stopRecording();
|
||||
}
|
||||
ListenerMap.checkErr();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Msg.error(this, "Failed during tear down: " + t);
|
||||
|
@ -226,8 +221,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
Map<Program, Set<LogicalBreakpoint>> breaksByProgramViaPer = new HashMap<>();
|
||||
for (Program prog : programManager.getAllOpenPrograms()) {
|
||||
Set<LogicalBreakpoint> breaks = new HashSet<>();
|
||||
for (Entry<Address, Set<LogicalBreakpoint>> ent : breakpointService
|
||||
.getBreakpoints(prog)
|
||||
for (Entry<Address, Set<LogicalBreakpoint>> ent : breakpointService.getBreakpoints(prog)
|
||||
.entrySet()) {
|
||||
for (LogicalBreakpoint lb : ent.getValue()) {
|
||||
ProgramLocation loc = lb.getProgramLocation();
|
||||
|
@ -264,8 +258,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
protected void addProgramTextBlock(Program p) throws Throwable {
|
||||
try (Transaction tx = program.openTransaction("Add .text block")) {
|
||||
p.getMemory()
|
||||
.createInitializedBlock(".text", addr(p, 0x00400000), 0x1000, (byte) 0,
|
||||
monitor, false);
|
||||
.createInitializedBlock(".text", addr(p, 0x00400000), 0x1000, (byte) 0, monitor,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,8 +284,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
DebuggerStaticMappingUtils.addMapping(
|
||||
new DefaultTraceLocation(t, null, textRegion.getLifespan(),
|
||||
textRegion.getMinAddress()),
|
||||
new ProgramLocation(p, addr(p, 0x00400000)), 0x1000,
|
||||
false);
|
||||
new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,8 +322,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
TargetBreakpointSpecContainer cont = getBreakpointContainer(r);
|
||||
cont.fetchElements().thenAccept(elements -> {
|
||||
for (TargetObject obj : elements.values()) {
|
||||
if (!(obj instanceof TargetBreakpointSpec) ||
|
||||
!(obj instanceof TargetDeletable)) {
|
||||
if (!(obj instanceof TargetBreakpointSpec) || !(obj instanceof TargetDeletable)) {
|
||||
continue;
|
||||
}
|
||||
TargetBreakpointSpec spec = (TargetBreakpointSpec) obj;
|
||||
|
@ -394,8 +386,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
int total) {
|
||||
assertEquals(total, breakpointService.getAllBreakpoints().size());
|
||||
|
||||
LogicalBreakpoint enLb = Unique
|
||||
.assertOne(breakpointService.getBreakpointsAt(trace, addr(trace, offset)));
|
||||
LogicalBreakpoint enLb =
|
||||
Unique.assertOne(breakpointService.getBreakpointsAt(trace, addr(trace, offset)));
|
||||
assertNull(enLb.getProgramLocation());
|
||||
assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), enLb.getKinds());
|
||||
|
||||
|
@ -958,10 +950,11 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
addTextMapping(recorder3, text3, program);
|
||||
waitForSwing();
|
||||
waitForPass(() -> {
|
||||
assertEquals(2, mappingService
|
||||
.getOpenMappedLocations(
|
||||
new ProgramLocation(program, addr(program, 0x00400123)))
|
||||
.size());
|
||||
assertEquals(2,
|
||||
mappingService
|
||||
.getOpenMappedLocations(
|
||||
new ProgramLocation(program, addr(program, 0x00400123)))
|
||||
.size());
|
||||
});
|
||||
waitForSwing();
|
||||
|
||||
|
@ -997,10 +990,11 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
addTextMapping(recorder3, text3, program);
|
||||
waitForSwing();
|
||||
waitForPass(() -> {
|
||||
assertEquals(2, mappingService
|
||||
.getOpenMappedLocations(
|
||||
new ProgramLocation(program, addr(program, 0x00400123)))
|
||||
.size());
|
||||
assertEquals(2,
|
||||
mappingService
|
||||
.getOpenMappedLocations(
|
||||
new ProgramLocation(program, addr(program, 0x00400123)))
|
||||
.size());
|
||||
});
|
||||
waitForSwing();
|
||||
|
||||
|
@ -1041,10 +1035,11 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
addTextMapping(recorder3, text3, program);
|
||||
waitForSwing();
|
||||
waitForPass(() -> {
|
||||
assertEquals(2, mappingService
|
||||
.getOpenMappedLocations(
|
||||
new ProgramLocation(program, addr(program, 0x00400123)))
|
||||
.size());
|
||||
assertEquals(2,
|
||||
mappingService
|
||||
.getOpenMappedLocations(
|
||||
new ProgramLocation(program, addr(program, 0x00400123)))
|
||||
.size());
|
||||
});
|
||||
waitForSwing();
|
||||
|
||||
|
@ -1094,10 +1089,11 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
addTextMapping(recorder3, text3, program);
|
||||
waitForSwing();
|
||||
waitForPass(() -> {
|
||||
assertEquals(2, mappingService
|
||||
.getOpenMappedLocations(
|
||||
new ProgramLocation(program, addr(program, 0x00400123)))
|
||||
.size());
|
||||
assertEquals(2,
|
||||
mappingService
|
||||
.getOpenMappedLocations(
|
||||
new ProgramLocation(program, addr(program, 0x00400123)))
|
||||
.size());
|
||||
});
|
||||
waitForSwing();
|
||||
|
||||
|
@ -1130,7 +1126,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
* breakpoints, so there's no context in which testing this service with aborted transactions on
|
||||
* breakpoints is sane.
|
||||
*/
|
||||
//@Test
|
||||
//@Test
|
||||
public void testAbortAddBreakpoint() throws Throwable {
|
||||
startRecorder1();
|
||||
Trace trace = recorder1.getTrace();
|
||||
|
@ -1548,8 +1544,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
DebuggerStaticMappingUtils.addMapping(
|
||||
new DefaultTraceLocation(tb.trace, null, textRegion.getLifespan(),
|
||||
textRegion.getMinAddress()),
|
||||
new ProgramLocation(p, addr(p, 0x00400000)), 0x1000,
|
||||
false);
|
||||
new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1621,8 +1616,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
@Test
|
||||
public void testAddTraceBreakpointSetSleighThenMapThenSaveToProgramCopiesSleigh()
|
||||
throws Throwable {
|
||||
DebuggerControlService editingService =
|
||||
addPlugin(tool, DebuggerControlServicePlugin.class);
|
||||
DebuggerControlService editingService = addPlugin(tool, DebuggerControlServicePlugin.class);
|
||||
|
||||
// TODO: What if already mapped?
|
||||
// Not sure I care about tb.setEmuSleigh() out of band
|
||||
|
@ -1637,8 +1631,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||
try (Transaction tid = tb.startTransaction()) {
|
||||
TraceBreakpoint bpt = tb.trace.getBreakpointManager()
|
||||
.addBreakpoint("Processes[1].Breakpoints[0]", Lifespan.nowOn(0),
|
||||
tb.addr(0x55550123),
|
||||
Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE),
|
||||
tb.addr(0x55550123), Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE),
|
||||
false /* emuEnabled defaults to true */, "");
|
||||
bpt.setEmuSleigh("r0=0xbeef;");
|
||||
}
|
||||
|
|
|
@ -32,12 +32,12 @@ import ghidra.util.datastruct.ListenerSet;
|
|||
public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectModel {
|
||||
public final Object lock = new Object();
|
||||
public final Object cbLock = new Object();
|
||||
protected final ExecutorService clientExecutor =
|
||||
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
||||
.namingPattern(getClass().getSimpleName() + "-thread-%d")
|
||||
protected final ExecutorService clientExecutor = Executors.newSingleThreadExecutor(
|
||||
new BasicThreadFactory.Builder().namingPattern(getClass().getSimpleName() + "-thread-%d")
|
||||
.build());
|
||||
|
||||
protected final ListenerSet<DebuggerModelListener> listeners =
|
||||
new ListenerSet<>(DebuggerModelListener.class, clientExecutor);
|
||||
new ListenerSet<>(DebuggerModelListener.class, true);
|
||||
|
||||
protected SpiTargetObject root;
|
||||
protected boolean rootAdded;
|
||||
|
@ -98,7 +98,10 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
|
|||
return null;
|
||||
});
|
||||
this.completedRoot.completeAsync(() -> root, clientExecutor);
|
||||
listeners.fire.rootAdded(root);
|
||||
|
||||
clientExecutor.execute(() -> {
|
||||
listeners.invoke().rootAdded(root);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,7 +203,7 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
|
|||
|
||||
/**
|
||||
* Ensure that dependent computations occur on the client executor
|
||||
*
|
||||
*
|
||||
* @param <T> the type of the future value
|
||||
* @param v the future
|
||||
* @return a future which completes after the given one on the client executor
|
||||
|
@ -249,13 +252,11 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
|
|||
}
|
||||
DefaultTargetObject<?, ?> dtoParent = (DefaultTargetObject<?, ?>) delegate;
|
||||
if (PathUtils.isIndex(path)) {
|
||||
dtoParent.changeElements(List.of(PathUtils.getIndex(path)), List.of(),
|
||||
"Replaced");
|
||||
dtoParent.changeElements(List.of(PathUtils.getIndex(path)), List.of(), "Replaced");
|
||||
}
|
||||
else {
|
||||
assert PathUtils.isName(path);
|
||||
dtoParent.changeAttributes(List.of(PathUtils.getKey(path)), Map.of(),
|
||||
"Replaced");
|
||||
dtoParent.changeAttributes(List.of(PathUtils.getKey(path)), Map.of(), "Replaced");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -304,6 +304,6 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
|
|||
|
||||
@Override
|
||||
public DebuggerModelListener broadcast() {
|
||||
return model.listeners.fire;
|
||||
return model.listeners.invoke();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -263,6 +263,6 @@ public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
|
|||
}
|
||||
|
||||
public DebuggerModelListener fire() {
|
||||
return listeners.fire;
|
||||
return listeners.invoke();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,14 +141,14 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
|||
|
||||
protected Set<DBTraceTimeViewport> viewports = new WeakHashCowSet<>();
|
||||
protected ListenerSet<DBTraceDirectChangeListener> directListeners =
|
||||
new ListenerSet<>(DBTraceDirectChangeListener.class);
|
||||
new ListenerSet<>(DBTraceDirectChangeListener.class, true);
|
||||
protected DBTraceVariableSnapProgramView programView;
|
||||
protected Set<DBTraceVariableSnapProgramView> programViews = new WeakHashCowSet<>();
|
||||
protected Set<TraceProgramView> programViewsView = Collections.unmodifiableSet(programViews);
|
||||
protected Map<Long, DBTraceProgramView> fixedProgramViews = new WeakValueHashCowMap<>();
|
||||
// NOTE: Can't pre-construct unmodifiableMap(fixedProgramViews), because values()' id changes
|
||||
protected ListenerSet<TraceProgramViewListener> viewListeners =
|
||||
new ListenerSet<>(TraceProgramViewListener.class);
|
||||
new ListenerSet<>(TraceProgramViewListener.class, true);
|
||||
|
||||
public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer)
|
||||
throws IOException, LanguageNotFoundException {
|
||||
|
@ -591,7 +591,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
|||
super.fireEvent(ev);
|
||||
if (directListeners != null) {
|
||||
// Some events fire during construction
|
||||
directListeners.fire.changed(ev);
|
||||
directListeners.invoke().changed(ev);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -613,7 +613,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
|||
return new DBTraceProgramView(this, snap, baseCompilerSpec);
|
||||
});
|
||||
}
|
||||
viewListeners.fire.viewCreated(view);
|
||||
viewListeners.invoke().viewCreated(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
@ -625,7 +625,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
|||
view = new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
|
||||
programViews.add(view);
|
||||
}
|
||||
viewListeners.fire.viewCreated(view);
|
||||
viewListeners.invoke().viewCreated(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
|
|||
*/
|
||||
protected final List<Lifespan> ordered = new ArrayList<>();
|
||||
protected final MutableLifeSet spanSet = new DefaultLifeSet();
|
||||
protected final ListenerSet<Runnable> changeListeners = new ListenerSet<>(Runnable.class);
|
||||
protected final ListenerSet<Runnable> changeListeners = new ListenerSet<>(Runnable.class, true);
|
||||
|
||||
protected long snap = 0;
|
||||
|
||||
|
@ -224,7 +224,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
|
|||
}
|
||||
}
|
||||
assert !ordered.isEmpty();
|
||||
changeListeners.fire.run();
|
||||
changeListeners.invoke().run();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -84,7 +84,7 @@ public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
|
|||
|
||||
double pos =
|
||||
span * (e.getX() - colX) / myViewCol.getWidth() + fullRangeDouble.min();
|
||||
listeners.fire.accept(pos);
|
||||
listeners.invoke().accept(pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,7 @@ public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
|
|||
private int savedViewColumn;
|
||||
|
||||
private final ForSeekMouseListener forSeekMouseListener = new ForSeekMouseListener();
|
||||
private final ListenerSet<SeekListener> listeners = new ListenerSet<>(SeekListener.class);
|
||||
private final ListenerSet<SeekListener> listeners = new ListenerSet<>(SeekListener.class, true);
|
||||
|
||||
public RangeCursorTableHeaderRenderer(N pos) {
|
||||
this.pos = pos;
|
||||
|
|
|
@ -24,6 +24,7 @@ public class DefaultObservableCollection<E, L extends CollectionChangeListener<?
|
|||
|
||||
protected enum Change {
|
||||
ADDED, REMOVED, MODIFIED;
|
||||
|
||||
static Change then(Change one, Change two) {
|
||||
if (one == null) {
|
||||
return two;
|
||||
|
@ -91,13 +92,13 @@ public class DefaultObservableCollection<E, L extends CollectionChangeListener<?
|
|||
for (Map.Entry<E, Change> ent : changes.entrySet()) {
|
||||
switch (ent.getValue()) {
|
||||
case ADDED:
|
||||
listeners.fire.elementAdded(ent.getKey());
|
||||
listeners.invoke().elementAdded(ent.getKey());
|
||||
break;
|
||||
case REMOVED:
|
||||
listeners.fire.elementRemoved(ent.getKey());
|
||||
listeners.invoke().elementRemoved(ent.getKey());
|
||||
break;
|
||||
case MODIFIED:
|
||||
listeners.fire.elementModified(ent.getKey());
|
||||
listeners.invoke().elementModified(ent.getKey());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +116,7 @@ public class DefaultObservableCollection<E, L extends CollectionChangeListener<?
|
|||
@Override
|
||||
public void close() {
|
||||
if (--aggregatorCount == 0) {
|
||||
l = listeners.fire;
|
||||
l = listeners.getProxy();
|
||||
changes.fire();
|
||||
}
|
||||
}
|
||||
|
@ -130,8 +131,8 @@ public class DefaultObservableCollection<E, L extends CollectionChangeListener<?
|
|||
|
||||
protected DefaultObservableCollection(Collection<E> wrapped, Class<L> listenerClass) {
|
||||
this.wrapped = wrapped;
|
||||
this.listeners = new ListenerSet<>(listenerClass);
|
||||
this.l = this.listeners.fire;
|
||||
this.listeners = new ListenerSet<>(listenerClass, true);
|
||||
this.l = this.listeners.getProxy();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,304 +0,0 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A map of listeners and a proxy for invoking each
|
||||
*
|
||||
* <P>
|
||||
* This is effectively a multiplexing primitive for a collection of listeners. The listeners may be
|
||||
* indexed by some key other than the listeners themselves. This is often useful if a filter or
|
||||
* wrapper is applied. If no wrapper is applied, consider using {@link ListenerSet} instead.
|
||||
* Additionally, the map is weak keyed, so that listeners are automatically removed if nothing else
|
||||
* maintain a strong reference.
|
||||
*
|
||||
* <P>
|
||||
* The proxy is accessed via the public {@link #fire} field. This implements the same interfaces as
|
||||
* each listener in the collection. Any method invoked on this proxy is invoked upon each listener
|
||||
* in the collection. If any invocation results in an unexpected exception, that exception is
|
||||
* logged, but otherwise ignored. This protects callbacks from errors introduced by other callbacks.
|
||||
* Expected exceptions are those declared in the {@code throws} clause of the invoked method. Such
|
||||
* an exception is immediately rethrown, preventing the execution of further callbacks. The default
|
||||
* implementation of {@link #createMap()} returns a synchronized map. The return value of any
|
||||
* invoked listener is ignored. Every invocation on the proxy returns null. As such, it is advisable
|
||||
* to only invoke proxy methods which return {@code void}.
|
||||
*
|
||||
* @param <K> the type of keys
|
||||
* @param <P> the interface of the proxy and multiplexed listeners
|
||||
* @param <V> the type of listeners
|
||||
*/
|
||||
public class ListenerMap<K, P, V extends P> {
|
||||
private static final boolean DEBUG_INCEPTION = false;
|
||||
|
||||
public static class ListenerEntry<V> extends WeakReference<V> {
|
||||
final String desc;
|
||||
final Throwable inception;
|
||||
|
||||
public ListenerEntry(V referent) {
|
||||
super(referent);
|
||||
this.desc = referent.toString();
|
||||
if (DEBUG_INCEPTION) {
|
||||
this.inception = new Throwable();
|
||||
}
|
||||
else {
|
||||
this.inception = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final Executor CALLING_THREAD = new Executor() {
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
command.run();
|
||||
}
|
||||
};
|
||||
|
||||
protected static final AtomicReference<Throwable> firstExc = new AtomicReference<>();
|
||||
|
||||
protected static void reportError(Object listener, Throwable e) {
|
||||
if (e instanceof RejectedExecutionException) {
|
||||
Msg.trace(listener, "Listener invocation rejected: " + e);
|
||||
}
|
||||
else {
|
||||
Msg.error(listener, "Listener " + listener + " caused unexpected exception", e);
|
||||
firstExc.accumulateAndGet(e, (o, n) -> o == null ? n : o);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the recorded exception.
|
||||
*
|
||||
* <P>
|
||||
* This method is for testing. If listeners are involved in a test, then this should be called
|
||||
* before that test.
|
||||
*
|
||||
* @see #checkErr()
|
||||
*/
|
||||
public static void clearErr() {
|
||||
firstExc.set(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and clear the recorded exception.
|
||||
*
|
||||
* <P>
|
||||
* This method is for testing. If listeners are involved in a test, then this should be called
|
||||
* after that test.
|
||||
*
|
||||
* <P>
|
||||
* Listeners are often invoked in threads off the test thread. Thus, if they generate an
|
||||
* exception, they get logged, but are otherwise ignored. In particular, a JUnit test with a
|
||||
* listener-generated exception will likely still pass (assuming no other assertion fails). This
|
||||
* method allows such exceptions to be detected and properly cause test failure. Note that this
|
||||
* only works for listeners derived from {@link ListenerMap}, including {@link ListenerSet}.
|
||||
* When an exception is logged, it is also recorded (statically) in the {@link ListenerMap}
|
||||
* class. Only the <em>first</em> unhandled exception is recorded. Subsequent exceptions are
|
||||
* logged, but ignored, until that first exception is cleared and/or checked.
|
||||
*/
|
||||
public static void checkErr() {
|
||||
Throwable exc = firstExc.getAndSet(null);
|
||||
if (exc != null) {
|
||||
throw new AssertionError("Listener caused an exception", exc);
|
||||
}
|
||||
}
|
||||
|
||||
protected class ListenerHandler<T extends P> implements InvocationHandler {
|
||||
protected final Class<T> ext;
|
||||
|
||||
public ListenerHandler(Class<T> ext) {
|
||||
this.ext = ext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
//Msg.debug(this, "Queuing invocation: " + method.getName() + " @" +
|
||||
// System.identityHashCode(executor));
|
||||
// Listener adds/removes need to take immediate effect, even with queued events
|
||||
executor.execute(() -> {
|
||||
Collection<? extends ListenerEntry<? extends V>> listenersVolatile;
|
||||
synchronized (lock) {
|
||||
listenersVolatile = map.values();
|
||||
}
|
||||
for (ListenerEntry<? extends V> wl : listenersVolatile) {
|
||||
V l = wl.get();
|
||||
if (l == null || !ext.isAssignableFrom(l.getClass())) {
|
||||
continue;
|
||||
}
|
||||
//Msg.debug(this,
|
||||
// "Invoking: " + method.getName() + " @" + System.identityHashCode(executor));
|
||||
try {
|
||||
method.invoke(l, args);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
reportError(l, cause);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
reportError(l, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
return null; // TODO: Assumes void return type
|
||||
}
|
||||
}
|
||||
|
||||
private final Object lock = new Object();
|
||||
private final Class<P> iface;
|
||||
private final Executor executor;
|
||||
private Map<K, ? extends ListenerEntry<? extends V>> map = createMap();
|
||||
|
||||
/**
|
||||
* A proxy which passes invocations to each value of this map
|
||||
*/
|
||||
public final P fire;
|
||||
|
||||
/**
|
||||
* A map of cached specialized proxies
|
||||
*/
|
||||
protected final Map<Class<? extends P>, P> extFires = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Construct a new map whose proxy implements the given interface
|
||||
*
|
||||
* <P>
|
||||
* The values in the map must implement the same interface.
|
||||
*
|
||||
* <P>
|
||||
* Callbacks will be serviced by the invoking thread. This may be risking if the invoking thread
|
||||
* is "precious" to the invoker. There is no guarantee callbacks into client code will complete
|
||||
* in a timely fashion.
|
||||
*
|
||||
* @param iface the interface to multiplex
|
||||
*/
|
||||
public ListenerMap(Class<P> iface) {
|
||||
this(iface, CALLING_THREAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new map whose proxy implements the given interface
|
||||
*
|
||||
* <P>
|
||||
* The values in the map must implement the same interface.
|
||||
*
|
||||
* @param iface the interface to multiplex
|
||||
*/
|
||||
public ListenerMap(Class<P> iface, Executor executor) {
|
||||
this.iface = Objects.requireNonNull(iface);
|
||||
this.executor = executor;
|
||||
this.fire = iface.cast(Proxy.newProxyInstance(this.getClass().getClassLoader(),
|
||||
new Class[] { iface }, new ListenerHandler<>(iface)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return map.toString();
|
||||
}
|
||||
|
||||
protected Map<K, ListenerEntry<? extends V>> createMap() {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
protected void notifyRemoved(ListenerEntry<? extends V> entry) {
|
||||
Msg.warn(this, "Listener garbage collected before removal: " + entry.desc);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends P> T fire(Class<T> ext) {
|
||||
if (ext == iface) {
|
||||
return ext.cast(fire);
|
||||
}
|
||||
if (!iface.isAssignableFrom(ext)) {
|
||||
throw new IllegalArgumentException("Cannot fire on less-specific interface");
|
||||
}
|
||||
return (T) extFires.computeIfAbsent(ext,
|
||||
e -> (P) Proxy.newProxyInstance(this.getClass().getClassLoader(),
|
||||
new Class<?>[] { iface, ext }, new ListenerHandler<>(ext)));
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
protected void doPutAllInto(Map<? super K, ? super ListenerEntry<? extends V>> newMap) {
|
||||
for (Entry<K, ? extends ListenerEntry<? extends V>> ent : map.entrySet()) {
|
||||
if (ent.getValue().get() == null) {
|
||||
notifyRemoved(ent.getValue());
|
||||
}
|
||||
else {
|
||||
newMap.put(ent.getKey(), ent.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public V put(K key, V val) {
|
||||
synchronized (lock) {
|
||||
if (map.get(key) == val) {
|
||||
return val;
|
||||
}
|
||||
Map<K, ListenerEntry<? extends V>> newMap = createMap();
|
||||
doPutAllInto(newMap);
|
||||
ListenerEntry<? extends V> result = newMap.put(key, new ListenerEntry<>(val));
|
||||
map = newMap;
|
||||
return result == null ? null : result.get();
|
||||
}
|
||||
}
|
||||
|
||||
public void putAll(ListenerMap<? extends K, P, ? extends V> that) {
|
||||
synchronized (lock) {
|
||||
Map<K, ListenerEntry<? extends V>> newMap = createMap();
|
||||
doPutAllInto(newMap);
|
||||
that.doPutAllInto(newMap);
|
||||
map = newMap;
|
||||
}
|
||||
}
|
||||
|
||||
public V get(K key) {
|
||||
ListenerEntry<? extends V> entry = map.get(key);
|
||||
return entry == null ? null : entry.get();
|
||||
}
|
||||
|
||||
public V remove(K key) {
|
||||
synchronized (lock) {
|
||||
if (!map.containsKey(key)) {
|
||||
return null;
|
||||
}
|
||||
Map<K, ListenerEntry<? extends V>> newMap = createMap();
|
||||
doPutAllInto(newMap);
|
||||
ListenerEntry<? extends V> result = newMap.remove(key);
|
||||
map = newMap;
|
||||
return result == null ? null : result.get();
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
synchronized (lock) {
|
||||
if (map.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
map = createMap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
|
||||
|
||||
/**
|
||||
* A weak set of multiplexed listeners and an invocation proxy
|
||||
*
|
||||
* @param <E> the type of multiplexed listeners
|
||||
*/
|
||||
public class ListenerSet<E> {
|
||||
public static final Executor CALLING_THREAD = ListenerMap.CALLING_THREAD;
|
||||
private final ListenerMap<E, E, E> map;
|
||||
|
||||
/**
|
||||
* A proxy which passes invocations to each member of this set
|
||||
*/
|
||||
public final E fire;
|
||||
|
||||
/**
|
||||
* Construct a new set whose elements and proxy implement the given interface
|
||||
*
|
||||
* <p>
|
||||
* Callbacks will be serviced by the invoking thread. This may be risking if the invoking thread
|
||||
* is "precious" to the invoker. There is no guarantee callbacks into client code will complete
|
||||
* in a timely fashion.
|
||||
*
|
||||
* @param iface the interface to multiplex
|
||||
*/
|
||||
public ListenerSet(Class<E> iface) {
|
||||
this(iface, CALLING_THREAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new set whose elements and proxy implement the given interface
|
||||
*
|
||||
* @param iface the interface to multiplex
|
||||
* @param executor an executor for servicing callbacks
|
||||
*/
|
||||
public ListenerSet(Class<E> iface, Executor executor) {
|
||||
map = new ListenerMap<E, E, E>(iface, executor) {
|
||||
@Override
|
||||
protected Map<E, ListenerEntry<? extends E>> createMap() {
|
||||
return ListenerSet.this.createMap();
|
||||
};
|
||||
};
|
||||
fire = map.fire;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return map.toString();
|
||||
}
|
||||
|
||||
protected Map<E, ListenerEntry<? extends E>> createMap() {
|
||||
// TODO: This doesn't prevent the automatic removal of an entry in the "immutable" map.
|
||||
return new WeakHashMap<>();
|
||||
}
|
||||
|
||||
public <T extends E> T fire(Class<T> ext) {
|
||||
return map.fire(ext);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
public boolean add(E e) {
|
||||
return map.put(e, e) != e;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addAll(ListenerSet<? extends E> c) {
|
||||
map.putAll((ListenerMap<? extends E, E, ? extends E>) c.map);
|
||||
}
|
||||
|
||||
public boolean remove(E e) {
|
||||
return map.remove(e) == e;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.util.datastruct;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
|
@ -28,6 +29,9 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
|||
*/
|
||||
public class PrivatelyQueuedListener<P> {
|
||||
|
||||
private ListenerErrorHandler errorHandler =
|
||||
DataStructureErrorHandlerFactory.createListenerErrorHandler();
|
||||
|
||||
protected class ListenerHandler implements InvocationHandler {
|
||||
private static final Method OBJECT_HASHCODE;
|
||||
static {
|
||||
|
@ -55,10 +59,10 @@ public class PrivatelyQueuedListener<P> {
|
|||
}
|
||||
catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
ListenerMap.reportError(out, cause);
|
||||
errorHandler.handleError(out, cause);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
ListenerMap.reportError(out, e);
|
||||
errorHandler.handleError(out, e);
|
||||
}
|
||||
});
|
||||
return null; // Assumes void return type
|
||||
|
@ -75,7 +79,7 @@ public class PrivatelyQueuedListener<P> {
|
|||
|
||||
/**
|
||||
* Create a new privately-queued listener which will invoke the given "output" listener
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Invoking the listener methods of {@link #in} will cause that invocation to be queued and
|
||||
* eventually delivered to the given output listener. Note, as a result, it is assumed all
|
||||
|
@ -83,7 +87,7 @@ public class PrivatelyQueuedListener<P> {
|
|||
* the invocation to complete, which defeats the purpose of the private queue. The invocations
|
||||
* on {@link #in} will always return {@code null}, which will cause an exception if the return
|
||||
* type is a different primitive.
|
||||
*
|
||||
*
|
||||
* @param iface the interface of the listener
|
||||
* @param executor the executor representing the processing queue
|
||||
* @param out the listener to receive the queued invocations
|
||||
|
@ -97,17 +101,17 @@ public class PrivatelyQueuedListener<P> {
|
|||
|
||||
/**
|
||||
* Create a new single-threaded privately-queued listener
|
||||
*
|
||||
* @see {@link #PrivatelyQueuedListener(Class, Executor, Object)}
|
||||
*
|
||||
* @param iface the interface of the listener
|
||||
* @param threadNamePattern a pattern for naming the single thread
|
||||
* @param out the listener to receive the queued invocations
|
||||
*/
|
||||
public PrivatelyQueuedListener(Class<P> iface, String threadNamePattern, P out) {
|
||||
this(iface,
|
||||
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
||||
.namingPattern(threadNamePattern)
|
||||
.build()),
|
||||
out);
|
||||
this(iface, Executors.newSingleThreadExecutor(
|
||||
new BasicThreadFactory.Builder().namingPattern(threadNamePattern).build()), out);
|
||||
}
|
||||
|
||||
public void setErrorHandler(ListenerErrorHandler errorHandler) {
|
||||
this.errorHandler = Objects.requireNonNull(errorHandler);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ListenerMapTest {
|
||||
public interface DummyListener {
|
||||
void event(String e);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBehavesLikeMap() {
|
||||
ListenerMap<String, DummyListener, DummyListener> listeners =
|
||||
new ListenerMap<>(DummyListener.class);
|
||||
DummyListener d1 = e -> {
|
||||
};
|
||||
DummyListener d2 = e -> {
|
||||
};
|
||||
listeners.put("Key1", d1);
|
||||
listeners.put("Key2", d2);
|
||||
assertEquals(d1, listeners.get("Key1"));
|
||||
assertEquals(d2, listeners.get("Key2"));
|
||||
listeners.put("Key1", d2);
|
||||
assertEquals(d2, listeners.get("Key1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiplexes() {
|
||||
ListenerMap<String, DummyListener, DummyListener> listeners =
|
||||
new ListenerMap<>(DummyListener.class);
|
||||
AtomicReference<String> ar1 = new AtomicReference<>();
|
||||
listeners.put("Key1", ar1::set);
|
||||
listeners.fire.event("EventA");
|
||||
assertEquals("EventA", ar1.get());
|
||||
AtomicReference<String> ar2 = new AtomicReference<>();
|
||||
listeners.put("Key2", ar2::set);
|
||||
listeners.fire.event("EventB");
|
||||
assertEquals("EventB", ar1.get());
|
||||
assertEquals("EventB", ar2.get());
|
||||
AtomicReference<String> ar3 = new AtomicReference<>();
|
||||
listeners.put("Key1", ar3::set); // Overwrite Key1
|
||||
listeners.fire.event("EventC");
|
||||
assertEquals("EventB", ar1.get());
|
||||
assertEquals("EventC", ar2.get());
|
||||
assertEquals("EventC", ar3.get());
|
||||
}
|
||||
|
||||
protected void waitEvents(Executor executor) throws Throwable {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
}, executor).get(1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddsRemovesImmediatelyEffective() throws Throwable {
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
CompletableFuture.runAsync(() -> Thread.currentThread().setName("ExecutorThread"), executor)
|
||||
.get();
|
||||
ListenerMap<String, DummyListener, DummyListener> listeners =
|
||||
new ListenerMap<>(DummyListener.class, executor);
|
||||
|
||||
Map<String, CompletableFuture<?>> stalls = Map.ofEntries(
|
||||
Map.entry("StallA", new CompletableFuture<>()),
|
||||
Map.entry("StallB", new CompletableFuture<>()),
|
||||
Map.entry("StallD", new CompletableFuture<>()));
|
||||
AtomicReference<String> ar1 = new AtomicReference<>();
|
||||
DummyListener l1 = s -> {
|
||||
CompletableFuture<?> stall = stalls.get(s);
|
||||
if (stall != null) {
|
||||
try {
|
||||
stall.get();
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
// Nothing I really can do
|
||||
}
|
||||
}
|
||||
ar1.set(s);
|
||||
};
|
||||
AtomicReference<String> ar2 = new AtomicReference<>();
|
||||
DummyListener l2 = ar2::set;
|
||||
|
||||
listeners.put("Key1", l1);
|
||||
ar1.set("None");
|
||||
listeners.fire.event("StallA");
|
||||
assertEquals("None", ar1.get());
|
||||
stalls.get("StallA").complete(null);
|
||||
waitEvents(executor);
|
||||
assertEquals("StallA", ar1.get());
|
||||
|
||||
// NB. It's the the fire timeline that matters, but the completion timeline
|
||||
listeners.fire.event("StallB");
|
||||
listeners.fire.event("EventC");
|
||||
listeners.put("Key2", l2);
|
||||
stalls.get("StallB").complete(null);
|
||||
waitEvents(executor);
|
||||
assertEquals("EventC", ar1.get());
|
||||
assertEquals("EventC", ar2.get());
|
||||
|
||||
listeners.fire.event("StallD");
|
||||
listeners.fire.event("EventE");
|
||||
listeners.remove("Key2");
|
||||
stalls.get("StallD").complete(null);
|
||||
waitEvents(executor);
|
||||
assertEquals("EventE", ar1.get());
|
||||
assertNotEquals("EventE", ar2.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContinuesOnError() {
|
||||
ListenerMap<String, DummyListener, DummyListener> listeners =
|
||||
new ListenerMap<>(DummyListener.class);
|
||||
|
||||
AtomicReference<String> ar1 = new AtomicReference<>();
|
||||
DummyListener d1 = e -> {
|
||||
ar1.set(e);
|
||||
throw new RuntimeException("It had better continue (1)");
|
||||
};
|
||||
listeners.put("Key1", d1);
|
||||
|
||||
AtomicReference<String> ar2 = new AtomicReference<>();
|
||||
DummyListener d2 = e -> {
|
||||
ar2.set(e);
|
||||
throw new RuntimeException("It had better continue (2)");
|
||||
};
|
||||
listeners.put("Key2", d2);
|
||||
|
||||
listeners.fire.event("Should see on both");
|
||||
assertEquals("Should see on both", ar1.get());
|
||||
assertEquals("Should see on both", ar2.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWeaklyReferencesListeners() {
|
||||
ListenerMap<String, DummyListener, DummyListener> listeners =
|
||||
new ListenerMap<>(DummyListener.class);
|
||||
|
||||
AtomicReference<String> ar1 = new AtomicReference<>();
|
||||
DummyListener d1 = e -> {
|
||||
ar1.set(e);
|
||||
throw new RuntimeException("It had better continue (1)");
|
||||
};
|
||||
listeners.put("Key1", d1);
|
||||
|
||||
listeners.fire.event("EventA");
|
||||
assertEquals("EventA", ar1.get());
|
||||
|
||||
d1 = null; // Trash the only strong reference
|
||||
System.gc();
|
||||
|
||||
listeners.fire.event("EventB");
|
||||
assertEquals("EventA", ar1.get());
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ListenerSetTest {
|
||||
public interface DummyListener {
|
||||
void event(String e);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBehavesLikeSetAndMultiplexes() {
|
||||
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class);
|
||||
AtomicInteger ai1 = new AtomicInteger();
|
||||
DummyListener d1 = e -> {
|
||||
ai1.getAndIncrement();
|
||||
};
|
||||
AtomicInteger ai2 = new AtomicInteger();
|
||||
DummyListener d2 = e -> {
|
||||
ai2.getAndIncrement();
|
||||
};
|
||||
listeners.add(d1);
|
||||
listeners.add(d2);
|
||||
|
||||
listeners.fire.event("EventA");
|
||||
assertEquals(1, ai1.get());
|
||||
assertEquals(1, ai2.get());
|
||||
|
||||
listeners.add(d1); // This had better not double fire
|
||||
|
||||
listeners.fire.event("EventB");
|
||||
assertEquals(2, ai1.get());
|
||||
assertEquals(2, ai2.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContinuesOnError() {
|
||||
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class);
|
||||
|
||||
AtomicReference<String> ar1 = new AtomicReference<>();
|
||||
DummyListener d1 = e -> {
|
||||
ar1.set(e);
|
||||
throw new RuntimeException("It had better continue (1)");
|
||||
};
|
||||
listeners.add(d1);
|
||||
|
||||
AtomicReference<String> ar2 = new AtomicReference<>();
|
||||
DummyListener d2 = e -> {
|
||||
ar2.set(e);
|
||||
throw new RuntimeException("It had better continue (2)");
|
||||
};
|
||||
listeners.add(d2);
|
||||
|
||||
listeners.fire.event("Should see on both");
|
||||
assertEquals("Should see on both", ar1.get());
|
||||
assertEquals("Should see on both", ar2.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWeaklyReferencesListeners() {
|
||||
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class);
|
||||
|
||||
AtomicReference<String> ar1 = new AtomicReference<>();
|
||||
DummyListener d1 = e -> {
|
||||
ar1.set(e);
|
||||
throw new RuntimeException("It had better continue (1)");
|
||||
};
|
||||
listeners.add(d1);
|
||||
|
||||
listeners.fire.event("EventA");
|
||||
assertEquals("EventA", ar1.get());
|
||||
|
||||
d1 = null; // Trash the only strong reference
|
||||
System.gc();
|
||||
|
||||
listeners.fire.event("EventB");
|
||||
assertEquals("EventA", ar1.get());
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ import java.util.*;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
/**
|
||||
* A class which handles exceptions that occur off of the main test thread. Exceptions can be
|
||||
|
@ -33,7 +33,7 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
|
|||
// Exception messages that we choose to ignore
|
||||
private static final String[] IGNORABLE_ERROR_MESSAGES =
|
||||
new String[] { "DerivedColor$UIResource cannot be cast to", // test machine timing issue
|
||||
"FontUIResource cannot be cast to javax.swing.Painter", // test machine timing issue
|
||||
"FontUIResource cannot be cast to javax.swing.Painter", // test machine timing issue
|
||||
};
|
||||
|
||||
private static final List<TestExceptionTracker> throwables =
|
||||
|
@ -41,13 +41,23 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
|
|||
|
||||
private static volatile boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Installs this exception handler as the default uncaught exception handler. See
|
||||
* {@link Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)}
|
||||
*/
|
||||
public static void registerHandler() {
|
||||
SystemUtilities.runSwingLater(() -> {
|
||||
// do this on the Swing thread
|
||||
// Note: not sure why this is done on the Swing thread later. Seems like this could be done
|
||||
// when this method is called, from any thread.
|
||||
Swing.runLater(() -> {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new ConcurrentTestExceptionHandler());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells this class to process the given throwable
|
||||
* @param thread the thread that encountered the throwable
|
||||
* @param t the throwable
|
||||
*/
|
||||
public synchronized static void handle(Thread thread, Throwable t) {
|
||||
|
||||
if (!enabled) {
|
||||
|
@ -72,10 +82,10 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
|
|||
|
||||
/**
|
||||
* Some exceptions that happen off the test thread are not serious enough to fail the test.
|
||||
* For example, some exceptions happen on the headless test server due more to
|
||||
* environmental issues rather than real problems. This method is intended to ignore
|
||||
* For example, some exceptions happen on the headless test server due more to
|
||||
* environmental issues rather than real problems. This method is intended to ignore
|
||||
* these less-than-serious issues.
|
||||
*
|
||||
*
|
||||
* @param t the throwable to examine
|
||||
* @return true if it should be ignored
|
||||
*/
|
||||
|
@ -89,26 +99,49 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
|
|||
return StringUtils.containsAny(message, IGNORABLE_ERROR_MESSAGES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all exceptions being tracked by this class
|
||||
*/
|
||||
public synchronized static void clear() {
|
||||
throwables.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables this class after a call to {@link #disable()} has been made
|
||||
*/
|
||||
public synchronized static void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables this class's tracking of exceptions. Clients use this method to have this class
|
||||
* ignore expected exceptions. This is a bit course-grained, as it does not allow clients to
|
||||
* ignore specific expected exceptions.
|
||||
*/
|
||||
public synchronized static void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this class is enabled. When disabled this class does not track exceptions.
|
||||
* @return true if enabled
|
||||
*/
|
||||
public synchronized static boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all exceptions tracked by this class
|
||||
* @return all exceptions tracked by this class
|
||||
*/
|
||||
public static synchronized List<TestExceptionTracker> getExceptions() {
|
||||
return new ArrayList<>(throwables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this class has been given any exceptions to handle since last being cleared
|
||||
* @return true if this class has been given any exceptions to handle since last being cleared
|
||||
*/
|
||||
public static synchronized boolean hasException() {
|
||||
return !throwables.isEmpty();
|
||||
}
|
||||
|
|
|
@ -38,14 +38,18 @@ class CopyOnReadWeakSet<T> extends WeakSet<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized void add(T t) {
|
||||
public synchronized boolean add(T t) {
|
||||
maybeWarnAboutAnonymousValue(t);
|
||||
boolean contains = weakHashStorage.containsKey(t);
|
||||
weakHashStorage.put(t, null);
|
||||
return !contains;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void remove(T t) {
|
||||
public synchronized boolean remove(Object t) {
|
||||
boolean contains = weakHashStorage.containsKey(t);
|
||||
weakHashStorage.remove(t);
|
||||
return contains;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,7 +68,7 @@ class CopyOnReadWeakSet<T> extends WeakSet<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean contains(T t) {
|
||||
public synchronized boolean contains(Object t) {
|
||||
return weakHashStorage.containsKey(t);
|
||||
}
|
||||
|
||||
|
@ -88,4 +92,32 @@ class CopyOnReadWeakSet<T> extends WeakSet<T> {
|
|||
return createCopy().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean addAll(Collection<? extends T> c) {
|
||||
boolean changed = false;
|
||||
for (T t : c) {
|
||||
changed |= add(t);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean retainAll(Collection<?> c) {
|
||||
boolean changed = false;
|
||||
Iterator<T> it = iterator();
|
||||
while (it.hasNext()) {
|
||||
T t = it.next();
|
||||
if (!c.contains(t)) {
|
||||
it.remove();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean removeAll(Collection<?> c) {
|
||||
return weakHashStorage.keySet().removeAll(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,38 +52,23 @@ class CopyOnWriteWeakSet<T> extends WeakSet<T> {
|
|||
return IteratorUtils.unmodifiableIterator(weakHashStorage.keySet().iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all items to this set.
|
||||
* <p>
|
||||
* Note: calling this method will only result in one copy operation. If {@link #add(Object)}
|
||||
* were called instead for each item of the iterator, then each call would copy this set.
|
||||
*
|
||||
* @param it the items
|
||||
*/
|
||||
@Override
|
||||
public synchronized void addAll(Iterable<T> it) {
|
||||
// only make one copy for the entire set of changes instead of for each change, as calling
|
||||
// add() would do
|
||||
WeakHashMap<T, T> newStorage = new WeakHashMap<>(weakHashStorage);
|
||||
for (T t : it) {
|
||||
newStorage.put(t, null);
|
||||
}
|
||||
weakHashStorage = newStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void add(T t) {
|
||||
public synchronized boolean add(T t) {
|
||||
maybeWarnAboutAnonymousValue(t);
|
||||
WeakHashMap<T, T> newStorage = new WeakHashMap<>(weakHashStorage);
|
||||
boolean contains = newStorage.containsKey(t);
|
||||
newStorage.put(t, null);
|
||||
weakHashStorage = newStorage;
|
||||
return !contains;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void remove(T t) {
|
||||
public synchronized boolean remove(Object t) {
|
||||
WeakHashMap<T, T> newStorage = new WeakHashMap<>(weakHashStorage);
|
||||
boolean contains = newStorage.containsKey(t);
|
||||
newStorage.remove(t);
|
||||
weakHashStorage = newStorage;
|
||||
return contains;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -107,7 +92,7 @@ class CopyOnWriteWeakSet<T> extends WeakSet<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(T t) {
|
||||
public boolean contains(Object t) {
|
||||
return weakHashStorage.containsKey(t);
|
||||
}
|
||||
|
||||
|
@ -120,4 +105,48 @@ class CopyOnWriteWeakSet<T> extends WeakSet<T> {
|
|||
public String toString() {
|
||||
return values().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all items to this set.
|
||||
* <p>
|
||||
* Note: calling this method will only result in one copy operation. If {@link #add(Object)}
|
||||
* were called instead for each item of the iterator, then each call would copy this set.
|
||||
*
|
||||
* @param c the items
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean addAll(Collection<? extends T> c) {
|
||||
// only make one copy for the entire set of changes instead of for each change, as calling
|
||||
// add() would do
|
||||
boolean changed = false;
|
||||
WeakHashMap<T, T> newStorage = new WeakHashMap<>(weakHashStorage);
|
||||
for (T t : c) {
|
||||
changed |= !newStorage.containsKey(t);
|
||||
newStorage.put(t, null);
|
||||
}
|
||||
weakHashStorage = newStorage;
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean retainAll(Collection<?> c) {
|
||||
WeakHashMap<T, T> newStorage = new WeakHashMap<>(weakHashStorage);
|
||||
boolean changed = false;
|
||||
for (T t : newStorage.keySet()) {
|
||||
if (!c.contains(t)) {
|
||||
newStorage.remove(t);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
weakHashStorage = newStorage;
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean removeAll(Collection<?> c) {
|
||||
WeakHashMap<T, T> newStorage = new WeakHashMap<>(weakHashStorage);
|
||||
boolean changed = newStorage.keySet().removeAll(c);
|
||||
weakHashStorage = newStorage;
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A class data structures can use to delegate error handling responsibilities to system-level
|
||||
* decision making. This allows for specialized error handling in testing mode.
|
||||
*/
|
||||
public class DataStructureErrorHandlerFactory {
|
||||
|
||||
// This field can be changed by the testing framework
|
||||
static ListenerErrorHandlerFactory listenerFactory = new ListenerErrorHandlerFactory() {
|
||||
@Override
|
||||
public ListenerErrorHandler createErrorHandler() {
|
||||
return new DefaultListenerErrorHandler();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a {@link ListenerErrorHandler}
|
||||
* @return the error handler
|
||||
*/
|
||||
public static ListenerErrorHandler createListenerErrorHandler() {
|
||||
return listenerFactory.createErrorHandler();
|
||||
}
|
||||
|
||||
private static class DefaultListenerErrorHandler implements ListenerErrorHandler {
|
||||
@Override
|
||||
public void handleError(Object listener, Throwable t) {
|
||||
Msg.error(listener, "Listener " + listener + " caused unexpected exception", t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
/**
|
||||
* A simple interface that allows listener structures to use different error handling
|
||||
*/
|
||||
public interface ListenerErrorHandler {
|
||||
|
||||
/**
|
||||
* Handles the given error
|
||||
* @param listener the listener that generated the error
|
||||
* @param t the error
|
||||
*/
|
||||
public void handleError(Object listener, Throwable t);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
/**
|
||||
* A simple interface for creating listener error handlers
|
||||
*/
|
||||
public interface ListenerErrorHandlerFactory {
|
||||
|
||||
/**
|
||||
* Creates the error handler
|
||||
* @return the error handler
|
||||
*/
|
||||
public ListenerErrorHandler createErrorHandler();
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A data structure meant to be used to hold listeners. This class has a few benefits:
|
||||
* <ul>
|
||||
* <li>Clients supply the class of the listeners being stored. Then, clients make use of a Java
|
||||
* {@link Proxy} object to sends events by calling the desired method directly on the proxy.
|
||||
* </li>
|
||||
* <li>This class is thread safe, allowing adding and removing listeners while events are being
|
||||
* fired.
|
||||
* </li>
|
||||
* <li>Weak or strong references may be used seamlessly by passing the correct constructor value.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Some restrictions:
|
||||
* <ul>
|
||||
* <li>Exception handling is currently done by storing the first exception encountered while
|
||||
* processing events. Any exception encountered while notifying a listener does not stop
|
||||
* follow-on listeners from getting notified.
|
||||
* </li>
|
||||
* <li>Listener classes are restricted to using methods with a void return type, as there is
|
||||
* currently no way to return values back to the client when notifying.
|
||||
* </li>
|
||||
* <li>The insertion order of listeners is not maintained, which means that event notification may
|
||||
* take place in an arbitrary order.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* An example use of this class to fire events could look like this:
|
||||
* <pre>
|
||||
* ListenerSet<ActionListener> listeners = new ListenerSet(ActionListener.class);
|
||||
* ActionEvent event = new ActionEvent(this, 1, "Event");
|
||||
* listeners.invoke().actionPerformed(event);
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> the listener type
|
||||
*/
|
||||
public class ListenerSet<T> {
|
||||
|
||||
/**
|
||||
* A proxy which passes invocations to each member of this set
|
||||
*/
|
||||
private final T proxy;
|
||||
private final ThreadSafeListenerStorage<T> listeners;
|
||||
|
||||
private ListenerErrorHandler errorHandler =
|
||||
DataStructureErrorHandlerFactory.createListenerErrorHandler();
|
||||
|
||||
/**
|
||||
* Constructs a listener set that is backed by weak references.
|
||||
* @param iface the listener class type.
|
||||
* @param isWeak true signals to use weak storage for the listeners. If using weak storage,
|
||||
* clients must keep a reference to the listener or it will eventually be removed from
|
||||
* this data structure when garbage collected.
|
||||
*/
|
||||
public ListenerSet(Class<T> iface, boolean isWeak) {
|
||||
Objects.requireNonNull(iface);
|
||||
this.proxy = iface.cast(Proxy.newProxyInstance(this.getClass().getClassLoader(),
|
||||
new Class[] { iface }, new ListenerHandler()));
|
||||
this.listeners = new ThreadSafeListenerStorage<>(isWeak);
|
||||
}
|
||||
|
||||
private class ListenerHandler implements InvocationHandler {
|
||||
@Override
|
||||
public Object invoke(Object proxyObject, Method method, Object[] args) throws Throwable {
|
||||
|
||||
listeners.forEach(listener -> {
|
||||
try {
|
||||
method.invoke(listener, args);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
errorHandler.handleError(listener, cause);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
errorHandler.handleError(listener, e);
|
||||
}
|
||||
});
|
||||
|
||||
return null; // assumes void return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the proxy object. Using this is the same as calling {@link #getProxy()}. Use this
|
||||
* method to make the client call more readable.
|
||||
*
|
||||
* @return the proxy
|
||||
*/
|
||||
public T invoke() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the proxy used by this class. Using {@link #invoke()} is preferred for better
|
||||
* readability.
|
||||
* @return the proxy
|
||||
*/
|
||||
public T getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return listeners.toString();
|
||||
}
|
||||
|
||||
public boolean add(T e) {
|
||||
return listeners.add(e);
|
||||
}
|
||||
|
||||
public boolean remove(T e) {
|
||||
return listeners.remove(e);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return listeners.size();
|
||||
}
|
||||
|
||||
public void setErrorHandler(ListenerErrorHandler errorHandler) {
|
||||
this.errorHandler = Objects.requireNonNull(errorHandler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import generic.cache.Factory;
|
||||
|
||||
/**
|
||||
* A very specific data structure that provides 'copy on write' behavior while the client is
|
||||
* iterating the elements.
|
||||
* <p>
|
||||
* This class is meant for a very narrow and specific use case that includes: having a relatively
|
||||
* small number of listeners and the need for only basic adding, removing and iterating.
|
||||
* <p>
|
||||
* This class will create a new copy of its internal storage for any write operation, but only if
|
||||
* that happens while the elements in this class are being iterated. This avoids unnecessary
|
||||
* copying.
|
||||
*
|
||||
* @param <T> the storage type
|
||||
*/
|
||||
class ThreadSafeListenerStorage<T> {
|
||||
|
||||
// Creates a new set and adds all values from the optional set argument
|
||||
private Factory<Set<T>, Set<T>> factory;
|
||||
private Set<T> storage;
|
||||
private AtomicInteger iteratorCount = new AtomicInteger();
|
||||
|
||||
ThreadSafeListenerStorage(boolean isWeak) {
|
||||
this(createFactory(isWeak));
|
||||
}
|
||||
|
||||
ThreadSafeListenerStorage(Factory<Set<T>, Set<T>> factory) {
|
||||
this.factory = factory;
|
||||
this.storage = factory.get(null);
|
||||
}
|
||||
|
||||
void forEach(Consumer<T> c) {
|
||||
|
||||
Set<T> toIterate = getSet();
|
||||
|
||||
try {
|
||||
for (T t : toIterate) {
|
||||
c.accept(t);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
iteratorCount.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized Set<T> getSet() {
|
||||
iteratorCount.incrementAndGet();
|
||||
return storage;
|
||||
}
|
||||
|
||||
synchronized boolean add(T t) {
|
||||
if (iteratorCount.get() != 0) {
|
||||
storage = factory.get(storage);
|
||||
}
|
||||
return storage.add(t);
|
||||
}
|
||||
|
||||
synchronized boolean remove(Object t) {
|
||||
if (iteratorCount.get() != 0) {
|
||||
storage = factory.get(storage);
|
||||
}
|
||||
return storage.remove(t);
|
||||
}
|
||||
|
||||
synchronized void clear() {
|
||||
storage = factory.get(null);
|
||||
}
|
||||
|
||||
synchronized int size() {
|
||||
return storage.size();
|
||||
}
|
||||
|
||||
private static <T> Factory<Set<T>, Set<T>> createFactory(boolean isWeak) {
|
||||
if (isWeak) {
|
||||
return new WeakSetFactory<T>();
|
||||
}
|
||||
return new StrongSetFactory<T>();
|
||||
}
|
||||
|
||||
private static class WeakSetFactory<T> implements Factory<Set<T>, Set<T>> {
|
||||
|
||||
@Override
|
||||
public Set<T> get(Set<T> set) {
|
||||
Set<T> newSet = new ThreadUnsafeWeakSet<>();
|
||||
if (set != null) {
|
||||
newSet.addAll(set);
|
||||
}
|
||||
return newSet;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class StrongSetFactory<T> implements Factory<Set<T>, Set<T>> {
|
||||
@Override
|
||||
public Set<T> get(Set<T> set) {
|
||||
Set<T> newSet = new HashSet<>();
|
||||
if (set != null) {
|
||||
newSet.addAll(set);
|
||||
}
|
||||
return newSet;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -26,14 +26,18 @@ class ThreadUnsafeWeakSet<T> extends WeakSet<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void add(T t) {
|
||||
public boolean add(T t) {
|
||||
maybeWarnAboutAnonymousValue(t);
|
||||
boolean contains = weakHashStorage.containsKey(t);
|
||||
weakHashStorage.put(t, null);
|
||||
return !contains;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(T t) {
|
||||
public boolean remove(Object t) {
|
||||
boolean contains = weakHashStorage.containsKey(t);
|
||||
weakHashStorage.remove(t);
|
||||
return contains;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,7 +66,7 @@ class ThreadUnsafeWeakSet<T> extends WeakSet<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(T t) {
|
||||
public boolean contains(Object t) {
|
||||
return weakHashStorage.containsKey(t);
|
||||
}
|
||||
|
||||
|
@ -76,4 +80,31 @@ class ThreadUnsafeWeakSet<T> extends WeakSet<T> {
|
|||
return values().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> c) {
|
||||
boolean changed = false;
|
||||
for (T t : c) {
|
||||
changed |= add(t);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
boolean changed = false;
|
||||
Iterator<T> it = iterator();
|
||||
while (it.hasNext()) {
|
||||
T t = it.next();
|
||||
if (!c.contains(t)) {
|
||||
it.remove();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return weakHashStorage.keySet().removeAll(c);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,16 +36,6 @@ public class WeakDataStructureFactory {
|
|||
return new ThreadUnsafeWeakSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to signal that the returned weak set is not thread safe and must be protected accordingly
|
||||
* when used in a multi-threaded environment.
|
||||
*
|
||||
* @return a new WeakSet
|
||||
*/
|
||||
public static <T> WeakSet<T> createThreadUnsafeWeakSet() {
|
||||
return new ThreadUnsafeWeakSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use when mutations outweigh iterations.
|
||||
*
|
||||
|
|
|
@ -17,14 +17,13 @@ package ghidra.util.datastruct;
|
|||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
public abstract class WeakSet<T> implements Iterable<T> {
|
||||
public abstract class WeakSet<T> implements Set<T> {
|
||||
|
||||
private static final boolean WARN_ON_ANONYMOUS_VALUE =
|
||||
SystemUtilities.isInDevelopmentMode() || SystemUtilities.isInTestingMode();
|
||||
|
@ -78,51 +77,47 @@ public abstract class WeakSet<T> implements Iterable<T> {
|
|||
// Interface Methods
|
||||
//==================================================================================================
|
||||
|
||||
/**
|
||||
* Adds all items to this set
|
||||
* @param it the items
|
||||
*/
|
||||
public void addAll(Iterable<T> it) {
|
||||
for (T t : it) {
|
||||
add(t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given object to the set
|
||||
* @param t the object to add
|
||||
*/
|
||||
public abstract void add(T t);
|
||||
@Override
|
||||
public abstract boolean add(T t);
|
||||
|
||||
/**
|
||||
* Remove the given object from the data structure
|
||||
* @param t the object to remove
|
||||
*
|
||||
*/
|
||||
public abstract void remove(T t);
|
||||
@Override
|
||||
public abstract boolean remove(Object t);
|
||||
|
||||
/**
|
||||
* Returns true if the given object is in this data structure
|
||||
* @param t the object
|
||||
* @return true if the given object is in this data structure
|
||||
*/
|
||||
public abstract boolean contains(T t);
|
||||
@Override
|
||||
public abstract boolean contains(Object t);
|
||||
|
||||
/**
|
||||
* Remove all elements from this data structure
|
||||
*/
|
||||
@Override
|
||||
public abstract void clear();
|
||||
|
||||
/**
|
||||
* Return the number of objects contained within this data structure
|
||||
* @return the size
|
||||
*/
|
||||
@Override
|
||||
public abstract int size();
|
||||
|
||||
/**
|
||||
* Return whether this data structure is empty
|
||||
* @return whether this data structure is empty
|
||||
*/
|
||||
@Override
|
||||
public abstract boolean isEmpty();
|
||||
|
||||
/**
|
||||
|
@ -132,9 +127,36 @@ public abstract class WeakSet<T> implements Iterable<T> {
|
|||
*/
|
||||
public abstract Collection<T> values();
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return weakHashStorage.keySet().toArray();
|
||||
}
|
||||
|
||||
// <T> is hiding the class declaration; it is needed to satisfy the interface
|
||||
@SuppressWarnings("hiding")
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return weakHashStorage.keySet().toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return weakHashStorage.keySet().containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract boolean addAll(Collection<? extends T> c);
|
||||
|
||||
@Override
|
||||
public abstract boolean retainAll(Collection<?> c);
|
||||
|
||||
@Override
|
||||
public abstract boolean removeAll(Collection<?> c);
|
||||
|
||||
/**
|
||||
* Returns a stream of the values of this collection.
|
||||
* @return a stream of the values of this collection.
|
||||
*/
|
||||
@Override
|
||||
public abstract Stream<T> stream();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.json.Json;
|
||||
import generic.test.AbstractGTest;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
public class ListenerSetTest {
|
||||
|
||||
@Test
|
||||
public void testListenerNotification() {
|
||||
|
||||
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
|
||||
|
||||
SpyDummyListener spy = new SpyDummyListener();
|
||||
listeners.add(spy);
|
||||
|
||||
String event = "Event";
|
||||
listeners.invoke().workDone(event);
|
||||
|
||||
assertEquals(1, spy.getEvents().size());
|
||||
assertEquals(event, spy.getEvents().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBehavesLikeSet() {
|
||||
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
|
||||
|
||||
SpyDummyListener l1 = new SpyDummyListener();
|
||||
SpyDummyListener l2 = new SpyDummyListener();
|
||||
|
||||
listeners.add(l1);
|
||||
listeners.add(l2);
|
||||
|
||||
listeners.invoke().workDone("EventA");
|
||||
assertEquals(1, l1.getEvents().size());
|
||||
assertEquals(1, l2.getEvents().size());
|
||||
|
||||
listeners.add(l1); // This had better not double fire
|
||||
|
||||
listeners.invoke().workDone("EventB");
|
||||
assertEquals(2, l1.getEvents().size());
|
||||
assertEquals(2, l2.getEvents().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContinuesOnError() {
|
||||
|
||||
// disable the default error reporting to avoid polluting the console
|
||||
Msg.setErrorLogger(new SpyErrorLogger());
|
||||
Msg.setErrorDisplay(new SpyErrorDisplay());
|
||||
|
||||
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
|
||||
|
||||
SpyDummyListener l1 = new SpyDummyListener() {
|
||||
@Override
|
||||
public void workDone(String e) {
|
||||
super.workDone(e);
|
||||
throw new RuntimeException("It had better continue (1)");
|
||||
}
|
||||
};
|
||||
|
||||
listeners.add(l1);
|
||||
|
||||
SpyDummyListener l2 = new SpyDummyListener() {
|
||||
@Override
|
||||
public void workDone(String e) {
|
||||
super.workDone(e);
|
||||
throw new RuntimeException("It had better continue (2)");
|
||||
}
|
||||
};
|
||||
|
||||
listeners.add(l2);
|
||||
|
||||
listeners.invoke().workDone("Should see on both");
|
||||
assertEquals("Should see on both", l1.getEvents().get(0));
|
||||
assertEquals("Should see on both", l2.getEvents().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWeaklyReferencesListeners() {
|
||||
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
|
||||
|
||||
SpyDummyListener l1 = new SpyDummyListener();
|
||||
listeners.add(l1);
|
||||
|
||||
listeners.invoke().workDone("EventA");
|
||||
assertEquals("EventA", l1.get());
|
||||
|
||||
l1 = null; // Trash the only strong reference
|
||||
|
||||
AbstractGTest.waitForCondition(() -> {
|
||||
System.gc();
|
||||
return listeners.size() == 0;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddWhileNotifying() throws Exception {
|
||||
|
||||
//
|
||||
// Test that any listeners added while notifying will not be notified and will not cause
|
||||
// exceptions.
|
||||
//
|
||||
|
||||
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
|
||||
SpyErrorHandler spyErrorHandler = new SpyErrorHandler();
|
||||
listeners.setErrorHandler(spyErrorHandler);
|
||||
|
||||
int n = 5;
|
||||
List<SpyDummyListener> originalListeners = createLatchedListeners(n);
|
||||
for (SpyDummyListener l : originalListeners) {
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
// notify in another thread to not block the test thread
|
||||
String event = "Event";
|
||||
Thread notifyThread = new Thread(() -> {
|
||||
listeners.invoke().workDone(event);
|
||||
});
|
||||
notifyThread.start();
|
||||
|
||||
List<SpyDummyListener> newListeners = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
LatchedSpyListener blockedListener = AbstractGTest.waitFor(() -> activeListener);
|
||||
activeListener = null;
|
||||
|
||||
// wait to ensure the listeners are being notified; mutate the listener list; tell the
|
||||
// listener being notified to continue;
|
||||
blockedListener.waitForStart();
|
||||
SpyDummyListener l = new SpyDummyListener();
|
||||
newListeners.add(l);
|
||||
listeners.add(l);
|
||||
blockedListener.proceed();
|
||||
}
|
||||
|
||||
notifyThread.join(2000);
|
||||
|
||||
for (SpyDummyListener l : originalListeners) {
|
||||
assertEquals(event, l.get());
|
||||
}
|
||||
|
||||
for (SpyDummyListener newListener : newListeners) {
|
||||
assertTrue(newListener.getEvents().isEmpty());
|
||||
}
|
||||
assertNull(spyErrorHandler.getException());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveWhileNotifying() throws Exception {
|
||||
|
||||
//
|
||||
// Test that any listeners removed while notifying will are still notified and will not
|
||||
// cause exceptions.
|
||||
//
|
||||
|
||||
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
|
||||
SpyErrorHandler spyErrorHandler = new SpyErrorHandler();
|
||||
listeners.setErrorHandler(spyErrorHandler);
|
||||
|
||||
int n = 5;
|
||||
List<SpyDummyListener> originalListeners = createLatchedListeners(n);
|
||||
for (SpyDummyListener l : originalListeners) {
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
// notify in another thread to not block the test thread
|
||||
String event = "Event";
|
||||
Thread notifyThread = new Thread(() -> {
|
||||
listeners.invoke().workDone(event);
|
||||
});
|
||||
notifyThread.start();
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
LatchedSpyListener blockedListener = AbstractGTest.waitFor(() -> activeListener);
|
||||
activeListener = null;
|
||||
|
||||
// wait to ensure the listeners are being notified; mutate the listener list; tell the
|
||||
// listener being notified to continue;
|
||||
blockedListener.waitForStart();
|
||||
listeners.remove(blockedListener);
|
||||
blockedListener.proceed();
|
||||
}
|
||||
|
||||
notifyThread.join(2000);
|
||||
|
||||
for (SpyDummyListener l : originalListeners) {
|
||||
assertEquals(event, l.get());
|
||||
}
|
||||
assertNull(spyErrorHandler.getException());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorReporting() {
|
||||
|
||||
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
|
||||
SpyErrorHandler spyErrorHandler = new SpyErrorHandler();
|
||||
listeners.setErrorHandler(spyErrorHandler);
|
||||
|
||||
listeners.add(new ExceptionalDummyListener());
|
||||
|
||||
String event = "Event";
|
||||
listeners.invoke().workDone(event);
|
||||
|
||||
assertNotNull(spyErrorHandler.getException());
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Thread-based Test Code
|
||||
//=================================================================================================
|
||||
|
||||
// variables only used by the thread-based tests
|
||||
private Throwable notificationException;
|
||||
private LatchedSpyListener activeListener;
|
||||
|
||||
private List<SpyDummyListener> createLatchedListeners(int n) {
|
||||
List<SpyDummyListener> list = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
list.add(new LatchedSpyListener());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private class LatchedSpyListener extends SpyDummyListener {
|
||||
|
||||
private CountDownLatch startedLatch = new CountDownLatch(1);
|
||||
private CountDownLatch proceedLatch = new CountDownLatch(1);
|
||||
|
||||
void proceed() {
|
||||
proceedLatch.countDown();
|
||||
}
|
||||
|
||||
void waitForStart() throws InterruptedException {
|
||||
assertTrue("Timed-out waiting for event notification",
|
||||
startedLatch.await(2, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void workDone(String e) {
|
||||
|
||||
activeListener = this;
|
||||
|
||||
if (notificationException != null) {
|
||||
return; // stop processing if the test fails
|
||||
}
|
||||
|
||||
startedLatch.countDown();
|
||||
super.workDone(e);
|
||||
try {
|
||||
if (!proceedLatch.await(2, TimeUnit.SECONDS)) {
|
||||
notificationException =
|
||||
new AssertException("Failed waiting to proceed in listener notificaiton");
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e1) {
|
||||
notificationException =
|
||||
new AssertException("Interrupted waiting to proceed in listener notification");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Dummy Listener
|
||||
//=================================================================================================
|
||||
|
||||
public interface DummyListener {
|
||||
void workDone(String e);
|
||||
}
|
||||
|
||||
private int id = 0;
|
||||
|
||||
private class SpyDummyListener implements DummyListener {
|
||||
|
||||
private List<String> events = new ArrayList<>();
|
||||
@SuppressWarnings("unused") // used by toString()
|
||||
private String name;
|
||||
|
||||
SpyDummyListener() {
|
||||
name = "Spy " + ++id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void workDone(String e) {
|
||||
events.add(e);
|
||||
}
|
||||
|
||||
String get() {
|
||||
if (events.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return events.get(0);
|
||||
}
|
||||
|
||||
List<String> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Inner Classes
|
||||
//=================================================================================================
|
||||
|
||||
private class ExceptionalDummyListener implements DummyListener {
|
||||
@Override
|
||||
public void workDone(String e) {
|
||||
throw new RuntimeException("Fail!");
|
||||
}
|
||||
}
|
||||
|
||||
private class SpyErrorHandler implements ListenerErrorHandler {
|
||||
|
||||
private Throwable exception;
|
||||
|
||||
@Override
|
||||
public void handleError(Object listener, Throwable t) {
|
||||
if (exception == null) {
|
||||
exception = t;
|
||||
}
|
||||
}
|
||||
|
||||
Throwable getException() {
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/* ###
|
||||
* 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.util.datastruct;
|
||||
|
||||
import generic.test.ConcurrentTestExceptionHandler;
|
||||
|
||||
/**
|
||||
* A utility that allows tests to set the error handling behavior for all data structures that
|
||||
* want flexible error handling. This class, in this package, allows us to override the factory
|
||||
* that is used to create the error handlers for framework listener data structures. The standard
|
||||
* behavior is to report errors to the the application log. Some clients wish to change this
|
||||
* behavior in testing mode so that any errors will fail tests. Without overriding this behavior,
|
||||
* unexpected errors during listener notification may be lost in the noise of the application log.
|
||||
* <p>
|
||||
* The {@link ConcurrentTestExceptionHandler} is the mechanism used to report errors. That class
|
||||
* allows the testing framework to synchronize error reporting, including to fail tests when errors
|
||||
* are encountered, in any thread. Tests can disable this failure behavior by calling
|
||||
* {@link ConcurrentTestExceptionHandler#disable()}. Doing so allows tests to prevent test failure
|
||||
* when encountering expected errors.
|
||||
*/
|
||||
public class TestDataStructureErrorHandlerInstaller {
|
||||
|
||||
public static void installConcurrentExceptionErrorHandler() {
|
||||
|
||||
DataStructureErrorHandlerFactory.listenerFactory = new ListenerErrorHandlerFactory() {
|
||||
@Override
|
||||
public ListenerErrorHandler createErrorHandler() {
|
||||
return new ConcurrentErrorHandler();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class ConcurrentErrorHandler implements ListenerErrorHandler {
|
||||
@Override
|
||||
public void handleError(Object listener, Throwable t) {
|
||||
ConcurrentTestExceptionHandler.handle(Thread.currentThread(), t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,64 +15,22 @@
|
|||
*/
|
||||
package ghidra.util.datastruct;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
|
||||
/**
|
||||
* Tests the {@link WeakSet} class.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @since Tracker Id 522
|
||||
*/
|
||||
public class WeakSetTest extends AbstractGenericTest {
|
||||
|
||||
/**
|
||||
* Creates an instance of this test class with the provided test name.
|
||||
*
|
||||
*/
|
||||
public WeakSetTest() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
// /*
|
||||
// * Test method for 'ghidra.util.WeakSet.WeakSet(Class)'
|
||||
// */
|
||||
// public void testConstructor() {
|
||||
// // valid instantiation
|
||||
// new WeakSet();
|
||||
//
|
||||
// // invalid parameter test
|
||||
// try {
|
||||
// new WeakSet( null );
|
||||
//
|
||||
// Assert.fail( "Passing null to the WeakSet's constructor did not trigger " +
|
||||
// "a NullPointerException." );
|
||||
// } catch ( NullPointerException npe ) {
|
||||
// // good, expected
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
* Test method for 'ghidra.util.WeakSet.add(Object)' and
|
||||
* 'ghidra.util.WeakSet.add(Object)'
|
||||
*/
|
||||
@Test
|
||||
public void testAddAndRemove() {
|
||||
WeakSet<String> weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet();
|
||||
|
@ -98,11 +56,6 @@ public class WeakSetTest extends AbstractGenericTest {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Test method for 'ghidra.util.WeakSet.clear()',
|
||||
* 'ghidra.util.WeakSet.clear()' and
|
||||
* 'ghidra.util.WeakSet.isEmpty()'
|
||||
*/
|
||||
@Test
|
||||
public void testClear() {
|
||||
WeakSet<String> weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet();
|
||||
|
@ -125,35 +78,32 @@ public class WeakSetTest extends AbstractGenericTest {
|
|||
weakSet.isEmpty());
|
||||
}
|
||||
|
||||
// /*
|
||||
// * Test method for 'ghidra.util.WeakSet.toArray()'
|
||||
// */
|
||||
// public void testToArray() {
|
||||
// WeakSet<String> weakSet = new WeakSet<String>();
|
||||
//
|
||||
// String[] values = { "one", "two", "three" };
|
||||
//
|
||||
// // test add
|
||||
// for (int i = 0; i < values.length; i++) {
|
||||
// weakSet.add( values[i] );
|
||||
// }
|
||||
//
|
||||
// assertTrue( "The weak set does not contain the correct number of " +
|
||||
// "elements after calling add().", (values.length==weakSet.size()) );
|
||||
//
|
||||
// Object[] valuesArray = weakSet.toArray();
|
||||
//
|
||||
// // check the array against our values
|
||||
// assertTrue( "The weak set returned a values array that is not the " +
|
||||
// "size as the number of values passed in.",
|
||||
// (valuesArray.length==values.length) );
|
||||
//
|
||||
// List valuesList = new ArrayList( Arrays.asList( values ) );
|
||||
// for (int i = 0; i < valuesArray.length; i++) {
|
||||
// assertTrue( "An element returned from the weak set was not " +
|
||||
// "passed to the set.", valuesList.contains( valuesArray[i] ) );
|
||||
// }
|
||||
// }
|
||||
@Test
|
||||
public void testToArray() {
|
||||
WeakSet<String> weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet();
|
||||
|
||||
String[] values = { "one", "two", "three" };
|
||||
|
||||
// test add
|
||||
for (String value : values) {
|
||||
weakSet.add(value);
|
||||
}
|
||||
|
||||
assertTrue("The weak set does not contain the correct number of " +
|
||||
"elements after calling add().", (values.length == weakSet.size()));
|
||||
|
||||
Object[] valuesArray = weakSet.toArray();
|
||||
|
||||
// check the array against our values
|
||||
assertTrue("The weak set returned a values array that is not the " +
|
||||
"size as the number of values passed in.", (valuesArray.length == values.length));
|
||||
|
||||
List<?> valuesList = new ArrayList<>(Arrays.asList(values));
|
||||
for (Object element : valuesArray) {
|
||||
assertTrue("An element returned from the weak set was not " + "passed to the set.",
|
||||
valuesList.contains(element));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Test method for 'ghidra.util.WeakSet.getListeners()'
|
||||
|
@ -187,66 +137,68 @@ public class WeakSetTest extends AbstractGenericTest {
|
|||
}
|
||||
|
||||
// Commented out because it was slow
|
||||
// @Test
|
||||
// public void testReferencesRemovedAfterCollection() {
|
||||
// // create some references and hold on to them to make sure that
|
||||
// // they are not collected and stay in the set
|
||||
// WeakSet<ActionListener> weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet();
|
||||
//
|
||||
// ActionListener[] values =
|
||||
// new ActionListener[] { new ActionListenerAdapter(), new ActionListenerAdapter(),
|
||||
// new ActionListenerAdapter(), new ActionListenerAdapter() };
|
||||
// ActionListener[] values = new ActionListener[] { new ActionListenerAdapter(),
|
||||
// new ActionListenerAdapter(), new ActionListenerAdapter(), new ActionListenerAdapter() };
|
||||
//
|
||||
// // test add
|
||||
// for (int i = 0; i < values.length; i++) {
|
||||
// weakSet.add(values[i]);
|
||||
// for (ActionListener value : values) {
|
||||
// weakSet.add(value);
|
||||
// }
|
||||
//
|
||||
// assertTrue("The weak set does not contain the correct number of "
|
||||
// + "elements after calling add().", (values.length == weakSet.size()));
|
||||
// assertTrue("The weak set does not contain the correct number of " +
|
||||
// "elements after calling add().", (values.length == weakSet.size()));
|
||||
//
|
||||
// // now release *all* those references
|
||||
// for (int i = 0; i < values.length; i++) {
|
||||
// values[i] = null;
|
||||
// }
|
||||
// values = null;
|
||||
//
|
||||
// // force garbage collection
|
||||
// // force garbage collection
|
||||
// forceGarbageCollection();
|
||||
//
|
||||
// // make sure that the unreferenced objects are removed from the set
|
||||
// assertTrue("The elements added to the weak set were not removed "
|
||||
// + "when they were no longer referenced.", (0 == weakSet.size()));
|
||||
// assertTrue("The elements added to the weak set were not removed " +
|
||||
// "when they were no longer referenced.", (0 == weakSet.size()));
|
||||
//
|
||||
// // now try the test again while only deleting some of the values
|
||||
// values =
|
||||
// new ActionListener[] { new ActionListenerAdapter(), new ActionListenerAdapter(),
|
||||
// new ActionListenerAdapter(), new ActionListenerAdapter() };
|
||||
// values = new ActionListener[] { new ActionListenerAdapter(), new ActionListenerAdapter(),
|
||||
// new ActionListenerAdapter(), new ActionListenerAdapter() };
|
||||
//
|
||||
// for (int i = 0; i < values.length; i++) {
|
||||
// weakSet.add(values[i]);
|
||||
// for (ActionListener value : values) {
|
||||
// weakSet.add(value);
|
||||
// }
|
||||
//
|
||||
// assertTrue("The weak set does not contain the correct number of "
|
||||
// + "elements after calling add().", (values.length == weakSet.size()));
|
||||
// assertTrue("The weak set does not contain the correct number of " +
|
||||
// "elements after calling add().", (values.length == weakSet.size()));
|
||||
//
|
||||
// // null out some values
|
||||
// values[0] = null;
|
||||
// values[2] = null;
|
||||
//
|
||||
// // force garbage collection
|
||||
// // force garbage collection
|
||||
// forceGarbageCollection();
|
||||
//
|
||||
// // make sure that the unreferenced objects are removed from the set
|
||||
// assertTrue("The elements added to the weak set were not removed "
|
||||
// + "when they were no longer referenced.", (2 == weakSet.size()));
|
||||
// assertTrue("The elements added to the weak set were not removed " +
|
||||
// "when they were no longer referenced.", (2 == weakSet.size()));
|
||||
// }
|
||||
//
|
||||
// class ActionListenerAdapter implements ActionListener {
|
||||
// @Override
|
||||
// public void actionPerformed(ActionEvent event) {
|
||||
// // stub implementation
|
||||
// }
|
||||
// }
|
||||
//
|
||||
class ActionListenerAdapter implements ActionListener {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
// stub implementation
|
||||
}
|
||||
}
|
||||
|
||||
// private void forceGarbageCollection() {
|
||||
// waitForSwing();
|
||||
//
|
||||
// System.gc();
|
||||
// System.gc();
|
||||
// try {
|
||||
|
|
Loading…
Reference in a new issue