GP-3512 - Created 'ListenerSet' for improved listener usage

This commit is contained in:
Dan 2023-09-15 15:11:57 -04:00 committed by dragonmacher
parent 41076f3af0
commit 08a900afad
77 changed files with 1669 additions and 1564 deletions

View file

@ -42,7 +42,7 @@ public class DbgDetachCommand extends AbstractDbgCommand<Void> {
manager.fireThreadExited(t.getId(), process, pending); manager.fireThreadExited(t.getId(), process, pending);
t.remove(); t.remove();
} }
manager.getEventListeners().fire.processRemoved(process.getId(), DbgCause.Causes.UNCLAIMED); manager.getEventListeners().invoke().processRemoved(process.getId(), DbgCause.Causes.UNCLAIMED);
return null; return null;
} }

View file

@ -29,7 +29,7 @@ public class DbgDebugInputCallbacks implements DebugInputCallbacks {
@Override @Override
public void startInput(long bufsize) { public void startInput(long bufsize) {
manager.getEventListeners().fire.promptChanged(">>>"); manager.getEventListeners().invoke().promptChanged(">>>");
CompletableFuture<String> cf = new CompletableFuture<String>(); CompletableFuture<String> cf = new CompletableFuture<String>();
try { try {
manager.setContinuation(cf); manager.setContinuation(cf);

View file

@ -206,7 +206,7 @@ public class DbgManagerImpl implements DbgManager {
private final Map<Class<?>, DebugStatus> statusMap = new LinkedHashMap<>(); private final Map<Class<?>, DebugStatus> statusMap = new LinkedHashMap<>();
private final Map<String, DebugStatus> statusByNameMap = new LinkedHashMap<>(); private final Map<String, DebugStatus> statusByNameMap = new LinkedHashMap<>();
private final ListenerSet<DbgEventsListener> listenersEvent = private final ListenerSet<DbgEventsListener> listenersEvent =
new ListenerSet<>(DbgEventsListener.class); new ListenerSet<>(DbgEventsListener.class, true);
private DebugEventInformation lastEventInformation; private DebugEventInformation lastEventInformation;
private DbgSession currentSession; private DbgSession currentSession;
@ -251,8 +251,8 @@ public class DbgManagerImpl implements DbgManager {
thread.add(); thread.add();
if (fire) { if (fire) {
Causes cause = DbgCause.Causes.UNCLAIMED; Causes cause = DbgCause.Causes.UNCLAIMED;
getEventListeners().fire.threadCreated(thread, cause); getEventListeners().invoke().threadCreated(thread, cause);
getEventListeners().fire.threadSelected(thread, null, cause); getEventListeners().invoke().threadSelected(thread, null, cause);
} }
return threads.get(id); return threads.get(id);
} }
@ -294,7 +294,7 @@ public class DbgManagerImpl implements DbgManager {
for (DebugThreadId tid : toRemove) { for (DebugThreadId tid : toRemove) {
removeThread(tid); 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 process the process that now has focus
* @param cause the cause of the focus change * @param cause the cause of the focus change
* @param fire signal listeners * @param forEach() signal listeners
* @return success status * @return success status
*/ */
@Override @Override
@ -326,7 +326,7 @@ public class DbgManagerImpl implements DbgManager {
DbgProcessImpl process = new DbgProcessImpl(this, id, pid, name); DbgProcessImpl process = new DbgProcessImpl(this, id, pid, name);
process.add(); process.add();
if (fire) { if (fire) {
getEventListeners().fire.processAdded(process, DbgCause.Causes.UNCLAIMED); getEventListeners().invoke().processAdded(process, DbgCause.Causes.UNCLAIMED);
} }
return processes.get(id); return processes.get(id);
} }
@ -344,7 +344,7 @@ public class DbgManagerImpl implements DbgManager {
if (sessions.remove(id) == null) { if (sessions.remove(id) == null) {
throw new IllegalArgumentException("There is no session with id " + id); 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); DbgSessionImpl session = new DbgSessionImpl(this, id);
session.add(); session.add();
if (fire) { if (fire) {
getEventListeners().fire.sessionAdded(session, DbgCause.Causes.UNCLAIMED); getEventListeners().invoke().sessionAdded(session, DbgCause.Causes.UNCLAIMED);
} }
} }
return sessions.get(id); return sessions.get(id);
@ -748,8 +748,8 @@ public class DbgManagerImpl implements DbgManager {
DebugBreakpoint bp = evt.getInfo(); DebugBreakpoint bp = evt.getInfo();
DbgBreakpointInfo info = new DbgBreakpointInfo(bp, getEventProcess(), getEventThread()); DbgBreakpointInfo info = new DbgBreakpointInfo(bp, getEventProcess(), getEventThread());
getEventListeners().fire.threadSelected(eventThread, null, evt.getCause()); getEventListeners().invoke().threadSelected(eventThread, null, evt.getCause());
getEventListeners().fire.breakpointHit(info, evt.getCause()); getEventListeners().invoke().breakpointHit(info, evt.getCause());
String key = Integer.toHexString(bp.getId()); String key = Integer.toHexString(bp.getId());
if (statusByNameMap.containsKey(key)) { if (statusByNameMap.containsKey(key)) {
@ -766,8 +766,8 @@ public class DbgManagerImpl implements DbgManager {
* @return retval handling/break status * @return retval handling/break status
*/ */
protected DebugStatus processException(DbgExceptionEvent evt, Void v) { protected DebugStatus processException(DbgExceptionEvent evt, Void v) {
getEventListeners().fire.eventSelected(evt, evt.getCause()); getEventListeners().invoke().eventSelected(evt, evt.getCause());
getEventListeners().fire.threadSelected(eventThread, null, evt.getCause()); getEventListeners().invoke().threadSelected(eventThread, null, evt.getCause());
DebugExceptionRecord64 info = evt.getInfo(); DebugExceptionRecord64 info = evt.getInfo();
String key = Integer.toHexString(info.code); String key = Integer.toHexString(info.code);
@ -789,9 +789,9 @@ public class DbgManagerImpl implements DbgManager {
DbgProcessImpl process = getCurrentProcess(); DbgProcessImpl process = getCurrentProcess();
DbgThreadImpl thread = getThreadFromDebugProcessInfo(process, evt.getInfo()); DbgThreadImpl thread = getThreadFromDebugProcessInfo(process, evt.getInfo());
getEventListeners().fire.eventSelected(evt, evt.getCause()); getEventListeners().invoke().eventSelected(evt, evt.getCause());
getEventListeners().fire.threadCreated(thread, DbgCause.Causes.UNCLAIMED); getEventListeners().invoke().threadCreated(thread, DbgCause.Causes.UNCLAIMED);
getEventListeners().fire.threadSelected(thread, null, evt.getCause()); getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
String key = eventId.id(); String key = eventId.id();
if (statusByNameMap.containsKey(key)) { if (statusByNameMap.containsKey(key)) {
@ -815,8 +815,8 @@ public class DbgManagerImpl implements DbgManager {
thread.remove(); thread.remove();
} }
process.threadExited(eventId); process.threadExited(eventId);
getEventListeners().fire.eventSelected(evt, evt.getCause()); getEventListeners().invoke().eventSelected(evt, evt.getCause());
getEventListeners().fire.threadExited(eventId, process, evt.getCause()); getEventListeners().invoke().threadExited(eventId, process, evt.getCause());
String key = eventId.id(); String key = eventId.id();
if (statusByNameMap.containsKey(key)) { if (statusByNameMap.containsKey(key)) {
@ -842,7 +842,7 @@ public class DbgManagerImpl implements DbgManager {
//currentThread = evt.getThread(); //currentThread = evt.getThread();
currentThread.setState(evt.getState(), evt.getCause(), evt.getReason()); 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(); String key = eventId.id();
if (statusByNameMap.containsKey(key)) { if (statusByNameMap.containsKey(key)) {
@ -861,9 +861,9 @@ public class DbgManagerImpl implements DbgManager {
protected DebugStatus processProcessCreated(DbgProcessCreatedEvent evt, Void v) { protected DebugStatus processProcessCreated(DbgProcessCreatedEvent evt, Void v) {
DebugProcessInfo info = evt.getInfo(); DebugProcessInfo info = evt.getInfo();
DbgProcessImpl proc = getProcessFromDebugProcessInfo(info); DbgProcessImpl proc = getProcessFromDebugProcessInfo(info);
getEventListeners().fire.eventSelected(evt, evt.getCause()); getEventListeners().invoke().eventSelected(evt, evt.getCause());
getEventListeners().fire.processAdded(proc, evt.getCause()); getEventListeners().invoke().processAdded(proc, evt.getCause());
getEventListeners().fire.processSelected(proc, evt.getCause()); getEventListeners().invoke().processSelected(proc, evt.getCause());
getThreadFromDebugProcessInfo(proc, info.initialThreadInfo); getThreadFromDebugProcessInfo(proc, info.initialThreadInfo);
//getEventListeners().fire.threadCreated(thread, evt.getCause()); //getEventListeners().fire.threadCreated(thread, evt.getCause());
@ -892,9 +892,9 @@ public class DbgManagerImpl implements DbgManager {
DbgProcessImpl process = getCurrentProcess(); DbgProcessImpl process = getCurrentProcess();
process.setExitCode(Long.valueOf(evt.getInfo())); process.setExitCode(Long.valueOf(evt.getInfo()));
getEventListeners().fire.eventSelected(evt, evt.getCause()); getEventListeners().invoke().eventSelected(evt, evt.getCause());
getEventListeners().fire.threadExited(eventId, process, evt.getCause()); getEventListeners().invoke().threadExited(eventId, process, evt.getCause());
getEventListeners().fire.processExited(process, evt.getCause()); getEventListeners().invoke().processExited(process, evt.getCause());
for (DebugBreakpoint bpt : getBreakpoints()) { for (DebugBreakpoint bpt : getBreakpoints()) {
breaksById.remove(bpt.getId()); breaksById.remove(bpt.getId());
@ -903,7 +903,7 @@ public class DbgManagerImpl implements DbgManager {
thread.remove(); thread.remove();
} }
process.remove(evt.getCause()); process.remove(evt.getCause());
getEventListeners().fire.processRemoved(process.getId(), evt.getCause()); getEventListeners().invoke().processRemoved(process.getId(), evt.getCause());
String key = process.getId().id(); String key = process.getId().id();
if (statusByNameMap.containsKey(key)) { if (statusByNameMap.containsKey(key)) {
@ -923,7 +923,7 @@ public class DbgManagerImpl implements DbgManager {
DebugThreadId eventId = updateState(); DebugThreadId eventId = updateState();
currentProcess = evt.getProcess(); currentProcess = evt.getProcess();
getEventListeners().fire.processSelected(currentProcess, evt.getCause()); getEventListeners().invoke().processSelected(currentProcess, evt.getCause());
String key = eventId.id(); String key = eventId.id();
if (statusByNameMap.containsKey(key)) { if (statusByNameMap.containsKey(key)) {
@ -944,8 +944,8 @@ public class DbgManagerImpl implements DbgManager {
DbgProcessImpl process = getCurrentProcess(); DbgProcessImpl process = getCurrentProcess();
DebugModuleInfo info = evt.getInfo(); DebugModuleInfo info = evt.getInfo();
process.moduleLoaded(info); process.moduleLoaded(info);
getEventListeners().fire.eventSelected(evt, evt.getCause()); getEventListeners().invoke().eventSelected(evt, evt.getCause());
getEventListeners().fire.moduleLoaded(process, info, evt.getCause()); getEventListeners().invoke().moduleLoaded(process, info, evt.getCause());
String key = info.getModuleName(); String key = info.getModuleName();
if (statusByNameMap.containsKey(key)) { if (statusByNameMap.containsKey(key)) {
@ -966,8 +966,8 @@ public class DbgManagerImpl implements DbgManager {
DbgProcessImpl process = getCurrentProcess(); DbgProcessImpl process = getCurrentProcess();
DebugModuleInfo info = evt.getInfo(); DebugModuleInfo info = evt.getInfo();
process.moduleUnloaded(info); process.moduleUnloaded(info);
getEventListeners().fire.eventSelected(evt, evt.getCause()); getEventListeners().invoke().eventSelected(evt, evt.getCause());
getEventListeners().fire.moduleUnloaded(process, info, evt.getCause()); getEventListeners().invoke().moduleUnloaded(process, info, evt.getCause());
String key = info.getModuleName(); String key = info.getModuleName();
if (statusByNameMap.containsKey(key)) { if (statusByNameMap.containsKey(key)) {
@ -1012,7 +1012,7 @@ public class DbgManagerImpl implements DbgManager {
//System.err.println("RUNNING " + id); //System.err.println("RUNNING " + id);
dbgState = DbgState.RUNNING; dbgState = DbgState.RUNNING;
// NB: Needed by GADP variants, but not IN-VM // NB: Needed by GADP variants, but not IN-VM
getEventListeners().fire.memoryChanged(currentProcess, 0L, 0, getEventListeners().invoke().memoryChanged(currentProcess, 0L, 0,
evt.getCause()); evt.getCause());
processEvent(new DbgRunningEvent(eventThread.getId())); processEvent(new DbgRunningEvent(eventThread.getId()));
} }
@ -1056,7 +1056,7 @@ public class DbgManagerImpl implements DbgManager {
if (key.value() == id) { if (key.value() == id) {
DbgThread thread = getThread(key); DbgThread thread = getThread(key);
if (thread != null) { if (thread != null) {
getEventListeners().fire.threadSelected(thread, null, evt.getCause()); getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
} }
processEvent(new DbgPromptChangedEvent(getControl().getPromptText())); processEvent(new DbgPromptChangedEvent(getControl().getPromptText()));
break; break;
@ -1080,7 +1080,7 @@ public class DbgManagerImpl implements DbgManager {
DebugThreadId eventId = updateState(); DebugThreadId eventId = updateState();
currentSession = evt.getSession(); currentSession = evt.getSession();
getEventListeners().fire.sessionSelected(currentSession, evt.getCause()); getEventListeners().invoke().sessionSelected(currentSession, evt.getCause());
String key = eventId.id(); String key = eventId.id();
if (statusByNameMap.containsKey(key)) { if (statusByNameMap.containsKey(key)) {
@ -1118,23 +1118,23 @@ public class DbgManagerImpl implements DbgManager {
protected void processDebuggeeStateChanged(DbgDebuggeeStateChangeEvent evt, Void v) { protected void processDebuggeeStateChanged(DbgDebuggeeStateChangeEvent evt, Void v) {
if (evt.getFlags().contains(ChangeDebuggeeState.DATA)) { 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) { 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(); String error = "SystemError " + evt.getError() + ":" + evt.getLevel();
getEventListeners().fire.consoleOutput(error, 0); getEventListeners().invoke().consoleOutput(error, 0);
} }
protected void processConsoleOutput(DbgConsoleOutputEvent evt, Void v) { protected void processConsoleOutput(DbgConsoleOutputEvent evt, Void v) {
getEventListeners().fire.eventSelected(evt, evt.getCause()); getEventListeners().invoke().eventSelected(evt, evt.getCause());
getEventListeners().fire.consoleOutput(evt.getInfo(), evt.getMask()); getEventListeners().invoke().consoleOutput(evt.getInfo(), evt.getMask());
} }
protected void processPromptChanged(DbgPromptChangedEvent evt, Void v) { 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 @Internal
public void doBreakpointCreated(DbgBreakpointInfo newInfo, DbgCause cause) { public void doBreakpointCreated(DbgBreakpointInfo newInfo, DbgCause cause) {
addKnownBreakpoint(newInfo, true); addKnownBreakpoint(newInfo, true);
getEventListeners().fire.breakpointCreated(newInfo, cause); getEventListeners().invoke().breakpointCreated(newInfo, cause);
} }
/** /**
@ -1227,7 +1227,7 @@ public class DbgManagerImpl implements DbgManager {
@Internal @Internal
public void doBreakpointModified(DbgBreakpointInfo newInfo, DbgCause cause) { public void doBreakpointModified(DbgBreakpointInfo newInfo, DbgCause cause) {
DbgBreakpointInfo oldInfo = addKnownBreakpoint(newInfo, true); 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) { if (oldInfo == null) {
return; return;
} }
getEventListeners().fire.breakpointDeleted(oldInfo, cause); getEventListeners().invoke().breakpointDeleted(oldInfo, cause);
oldInfo.dispose(); oldInfo.dispose();
} }
@ -1251,7 +1251,7 @@ public class DbgManagerImpl implements DbgManager {
if (Objects.equals(newInfo, oldInfo)) { if (Objects.equals(newInfo, oldInfo)) {
return; return;
} }
getEventListeners().fire.breakpointModified(newInfo, oldInfo, cause); getEventListeners().invoke().breakpointModified(newInfo, oldInfo, cause);
} }
@Internal @Internal
@ -1637,7 +1637,7 @@ public class DbgManagerImpl implements DbgManager {
} }
public void fireThreadExited(DebugThreadId id, DbgProcessImpl process, DbgCause cause) { public void fireThreadExited(DebugThreadId id, DbgProcessImpl process, DbgCause cause) {
getEventListeners().fire.threadExited(id, process, cause); getEventListeners().invoke().threadExited(id, process, cause);
} }
@Override @Override
@ -1771,7 +1771,7 @@ public class DbgManagerImpl implements DbgManager {
if (mirror != null) { if (mirror != null) {
mirror.setOffset(currentProcess.getOffset()); mirror.setOffset(currentProcess.getOffset());
currentProcess = eventProcess = mirror; 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) { if (mirror != null) {
mirror.setOffset(currentThread.getOffset()); mirror.setOffset(currentThread.getOffset());
currentThread = eventThread = mirror; 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); eventProcess = getProcessComputeIfAbsent(epid, so.getCurrentProcessSystemId(), so.getCurrentProcessExecutableName(), true);
currentThread = eventThread = getThreadComputeIfAbsent(etid, (DbgProcessImpl) eventProcess, currentThread = eventThread = getThreadComputeIfAbsent(etid, (DbgProcessImpl) eventProcess,
so.getCurrentThreadSystemId(), false); so.getCurrentThreadSystemId(), false);
getEventListeners().fire.threadSelected(eventThread, null, Causes.UNCLAIMED); getEventListeners().invoke().threadSelected(eventThread, null, Causes.UNCLAIMED);
} }
} }

View file

@ -58,7 +58,7 @@ public class DbgModuleImpl implements DbgModule {
*/ */
public void add() { public void add() {
process.addModule(this); 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() { public void remove() {
process.removeModule(name); process.removeModule(name);
manager.getEventListeners().fire.moduleUnloaded(process, info, Causes.UNCLAIMED); manager.getEventListeners().invoke().moduleUnloaded(process, info, Causes.UNCLAIMED);
} }
@Override @Override

View file

@ -93,7 +93,7 @@ public class DbgThreadImpl implements DbgThread {
//manager.getEventListeners().fire.threadCreated(this, DbgCause.Causes.UNCLAIMED); //manager.getEventListeners().fire.threadCreated(this, DbgCause.Causes.UNCLAIMED);
process.addThread(this); process.addThread(this);
state.addChangeListener((oldState, newState, pair) -> { 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); pair.reason);
}); });
} }

View file

@ -207,8 +207,8 @@ public interface DbgModelTargetBreakpointSpec extends //
public default void breakpointHit() { public default void breakpointHit() {
DbgModelTargetThread targetThread = DbgModelTargetThread targetThread =
getParentProcess().getThreads().getTargetThread(getManager().getEventThread()); getParentProcess().getThreads().getTargetThread(getManager().getEventThread());
getActions().fire.breakpointHit((DbgModelTargetBreakpointSpec) getProxy(), targetThread, getActions().invoke()
null, this); .breakpointHit((DbgModelTargetBreakpointSpec) getProxy(), targetThread, null, this);
} }
} }

View file

@ -124,7 +124,7 @@ public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, Target
getParentThread().getThread().writeRegisters(toWrite).handle(seq::next); getParentThread().getThread().writeRegisters(toWrite).handle(seq::next);
// TODO: Should probably filter only effective and normalized writes in the callback // TODO: Should probably filter only effective and normalized writes in the callback
}).then(seq -> { }).then(seq -> {
manager.getEventListeners().fire.threadStateChanged(thread, thread.getState(), manager.getEventListeners().invoke().threadStateChanged(thread, thread.getState(),
DbgCause.Causes.UNCLAIMED, DbgReason.Reasons.NONE); DbgCause.Causes.UNCLAIMED, DbgReason.Reasons.NONE);
broadcast().registersUpdated(getProxy(), values); broadcast().registersUpdated(getProxy(), values);
seq.exit(); seq.exit();

View file

@ -110,7 +110,7 @@ public class DbgModelImpl extends AbstractDbgModel implements DebuggerObjectMode
@Override @Override
public void terminate() throws IOException { public void terminate() throws IOException {
listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL);
root.invalidateSubtree(root, "Dbgeng is terminating"); root.invalidateSubtree(root, "Dbgeng is terminating");
dbg.terminate(); dbg.terminate();
} }

View file

@ -15,7 +15,8 @@
*/ */
package agent.dbgeng.model.impl; package agent.dbgeng.model.impl;
import java.util.*; import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import agent.dbgeng.manager.breakpoint.DbgBreakpointInfo; 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.TargetAttributeType;
import ghidra.dbg.target.schema.TargetObjectSchemaInfo; import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.ListenerSet;
@TargetObjectSchemaInfo( @TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = { //
name = "BreakpointSpec",
attributes = { //
@TargetAttributeType( // @TargetAttributeType( //
name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, // name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, //
type = DbgModelTargetBreakpointContainerImpl.class), // type = DbgModelTargetBreakpointContainerImpl.class), //
@TargetAttributeType( // @TargetAttributeType( //
name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, // name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, //
type = DbgModelTargetBreakpointSpecImpl.class), // type = DbgModelTargetBreakpointSpecImpl.class), //
@TargetAttributeType( @TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_TYPE_ATTRIBUTE_NAME, type = String.class), //
name = DbgModelTargetBreakpointSpecImpl.BPT_TYPE_ATTRIBUTE_NAME, @TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_DISP_ATTRIBUTE_NAME, type = String.class), //
type = String.class), // @TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_PENDING_ATTRIBUTE_NAME, type = String.class), //
@TargetAttributeType( @TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_TIMES_ATTRIBUTE_NAME, type = Integer.class), //
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) // @TargetAttributeType(type = Void.class) //
}, }, canonicalContainer = true)
canonicalContainer = true)
public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetBreakpointSpec { implements DbgModelTargetBreakpointSpec {
@ -69,28 +58,21 @@ public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl
protected boolean enabled; protected boolean enabled;
public void changeAttributeSet(String reason) { public void changeAttributeSet(String reason) {
this.changeAttributes(List.of(), List.of(), Map.of( this.changeAttributes(List.of(), List.of(),
DISPLAY_ATTRIBUTE_NAME, "[" + info.getNumber() + "] " + info.getExpression(), Map.of(DISPLAY_ATTRIBUTE_NAME, "[" + info.getNumber() + "] " + info.getExpression(),
RANGE_ATTRIBUTE_NAME, doGetRange(), RANGE_ATTRIBUTE_NAME, doGetRange(), SPEC_ATTRIBUTE_NAME, this,
SPEC_ATTRIBUTE_NAME, this, EXPRESSION_ATTRIBUTE_NAME, info.getExpression(), KINDS_ATTRIBUTE_NAME, getKinds(),
EXPRESSION_ATTRIBUTE_NAME, info.getExpression(),
KINDS_ATTRIBUTE_NAME, getKinds(),
BPT_TYPE_ATTRIBUTE_NAME, info.getType().name(), BPT_TYPE_ATTRIBUTE_NAME, info.getType().name(), BPT_DISP_ATTRIBUTE_NAME,
BPT_DISP_ATTRIBUTE_NAME, info.getDisp().name(), info.getDisp().name(), BPT_PENDING_ATTRIBUTE_NAME, info.getPending(),
BPT_PENDING_ATTRIBUTE_NAME, info.getPending(),
BPT_TIMES_ATTRIBUTE_NAME, info.getTimes()), BPT_TIMES_ATTRIBUTE_NAME, info.getTimes()),
reason); reason);
} }
private final ListenerSet<TargetBreakpointAction> actions =
new ListenerSet<>(TargetBreakpointAction.class) {
// Use strong references on actions // Use strong references on actions
// The values may be weak, but the keys, which are the same objects, are strong // The values may be weak, but the keys, which are the same objects, are strong
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() { private final ListenerSet<TargetBreakpointAction> actions =
return new LinkedHashMap<>(); new ListenerSet<>(TargetBreakpointAction.class, false);
}
};
public DbgModelTargetBreakpointSpecImpl(DbgModelTargetBreakpointContainer breakpoints, public DbgModelTargetBreakpointSpecImpl(DbgModelTargetBreakpointContainer breakpoints,
DbgBreakpointInfo info) { DbgBreakpointInfo info) {

View file

@ -109,8 +109,10 @@ public class DbgModelTargetExceptionImpl extends DbgModelTargetObjectImpl
.setFocus(this); .setFocus(this);
changeAttributes(List.of(), List.of(), Map.of( // changeAttributes(List.of(), List.of(), Map.of( //
MODIFIED_ATTRIBUTE_NAME, true), "Refreshed"); MODIFIED_ATTRIBUTE_NAME, true), "Refreshed");
manager.getEventListeners().fire.consoleOutput( manager.getEventListeners()
"Exception " + filter.getExceptionCode() + " : " + filter.getName() + "\n", 0); .invoke()
.consoleOutput("Exception " + filter.getExceptionCode() + " : " +
filter.getName() + "\n", 0);
} }
} }
} }

View file

@ -162,7 +162,7 @@ public class DbgModelTargetRegisterContainerImpl extends DbgModelTargetObjectImp
return thread.writeRegisters(toWrite); return thread.writeRegisters(toWrite);
// TODO: Should probably filter only effective and normalized writes in the callback // TODO: Should probably filter only effective and normalized writes in the callback
}).thenAccept(__ -> { }).thenAccept(__ -> {
manager.getEventListeners().fire.threadStateChanged(thread, thread.getState(), manager.getEventListeners().invoke().threadStateChanged(thread, thread.getState(),
DbgCause.Causes.UNCLAIMED, DbgReason.Reasons.NONE); DbgCause.Causes.UNCLAIMED, DbgReason.Reasons.NONE);
broadcast().registersUpdated(getProxy(), values); broadcast().registersUpdated(getProxy(), values);
})); }));

View file

@ -121,7 +121,7 @@ public class DbgModel2Impl extends AbstractDbgModel
@Override @Override
public void terminate() throws IOException { public void terminate() throws IOException {
listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL);
root.invalidateSubtree(root, "Dbgmodel is terminating"); root.invalidateSubtree(root, "Dbgmodel is terminating");
dbg.terminate(); dbg.terminate();
} }

View file

@ -31,7 +31,6 @@ import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointAction; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointAction;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.ListenerSet;
public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl implements // public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl implements //
@ -69,7 +68,8 @@ 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) { switch (type) {
case "Available": case "Available":
return DbgModelTargetAvailableContainer.class; return DbgModelTargetAvailableContainer.class;
@ -133,15 +133,16 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
return null; return null;
} }
public static DbgModelTargetObject makeProxy(DbgModel2Impl model, DbgModelTargetObject parent, String key, public static DbgModelTargetObject makeProxy(DbgModel2Impl model, DbgModelTargetObject parent,
ModelObject object) { String key, ModelObject object) {
List<Class<? extends TargetObject>> mixins = new ArrayList<>(); List<Class<? extends TargetObject>> mixins = new ArrayList<>();
String lkey = key; String lkey = key;
String pname = parent.getName(); String pname = parent.getName();
if (object.getKind().equals(ModelObjectKind.OBJECT_METHOD)) { if (object.getKind().equals(ModelObjectKind.OBJECT_METHOD)) {
mixins.add(DbgModelTargetMethod.class); mixins.add(DbgModelTargetMethod.class);
} else { }
else {
Class<? extends DbgModelTargetObject> mixin = lookupWrapperType(lkey, pname); Class<? extends DbgModelTargetObject> mixin = lookupWrapperType(lkey, pname);
if (mixin != null) { if (mixin != null) {
mixins.add(mixin); mixins.add(mixin);
@ -161,12 +162,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
private boolean breakpointEnabled; private boolean breakpointEnabled;
private final ListenerSet<TargetBreakpointAction> breakpointActions = private final ListenerSet<TargetBreakpointAction> breakpointActions =
new ListenerSet<>(TargetBreakpointAction.class) { new ListenerSet<>(TargetBreakpointAction.class, false);
// Use strong references on actions
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
return new LinkedHashMap<>();
}
};
// Extending DefaultTargetObject may spare you from listeners, elements, and // Extending DefaultTargetObject may spare you from listeners, elements, and
// attributes // attributes
@ -175,8 +171,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
// any other fields you need to support your impl // any other fields you need to support your impl
public DelegateDbgModel2TargetObject(DbgModel2Impl model, DbgModelTargetObject parent, String key, public DelegateDbgModel2TargetObject(DbgModel2Impl model, DbgModelTargetObject parent,
ModelObject modelObject, List<Class<? extends TargetObject>> mixins) { String key, ModelObject modelObject, List<Class<? extends TargetObject>> mixins) {
super(model, mixins, model, parent.getProxy(), key, getHintForObject(modelObject)); super(model, mixins, model, parent.getProxy(), key, getHintForObject(modelObject));
// System.err.println(this); // System.err.println(this);
this.state = new ProxyState(model, modelObject); this.state = new ProxyState(model, modelObject);
@ -198,8 +194,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
if (mixin != null) { if (mixin != null) {
mixins.add(mixin); mixins.add(mixin);
} }
DelegateDbgModel2TargetObject delegate = new DelegateDbgModel2TargetObject(getModel(), p, key, modelObject, DelegateDbgModel2TargetObject delegate =
mixins); new DelegateDbgModel2TargetObject(getModel(), p, key, modelObject, mixins);
return delegate; return delegate;
} }
@ -420,16 +416,18 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
List<DelegateDbgModel2TargetObject> delegates = new ArrayList<>(); List<DelegateDbgModel2TargetObject> delegates = new ArrayList<>();
TargetObject stack = (TargetObject) getCachedAttribute("Stack"); TargetObject stack = (TargetObject) getCachedAttribute("Stack");
if (stack != null) { if (stack != null) {
DbgModelTargetStack frames = (DbgModelTargetStack) stack.getCachedAttribute("Frames"); DbgModelTargetStack frames =
(DbgModelTargetStack) stack.getCachedAttribute("Frames");
delegates.add((DelegateDbgModel2TargetObject) frames.getDelegate()); delegates.add((DelegateDbgModel2TargetObject) frames.getDelegate());
} }
DbgModelTargetRegisterContainer container = (DbgModelTargetRegisterContainer) getCachedAttribute( DbgModelTargetRegisterContainer container =
"Registers"); (DbgModelTargetRegisterContainer) getCachedAttribute("Registers");
if (container == null) { if (container == null) {
return; return;
} }
delegates.add((DelegateDbgModel2TargetObject) container.getDelegate()); delegates.add((DelegateDbgModel2TargetObject) container.getDelegate());
DbgModelTargetRegisterBank bank = (DbgModelTargetRegisterBank) container.getCachedAttribute("User"); DbgModelTargetRegisterBank bank =
(DbgModelTargetRegisterBank) container.getCachedAttribute("User");
if (bank == null) { if (bank == null) {
return; return;
} }
@ -457,7 +455,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
for (TargetObject obj : getCachedElements().values()) { for (TargetObject obj : getCachedElements().values()) {
if (obj instanceof TargetStackFrame) { if (obj instanceof TargetStackFrame) {
DbgModelTargetObject frame = (DbgModelTargetObject) obj; DbgModelTargetObject frame = (DbgModelTargetObject) obj;
DelegateDbgModel2TargetObject delegate = (DelegateDbgModel2TargetObject) frame.getDelegate(); DelegateDbgModel2TargetObject delegate =
(DelegateDbgModel2TargetObject) frame.getDelegate();
delegate.threadStateChangedSpecific(state, reason); delegate.threadStateChangedSpecific(state, reason);
} }
} }

View file

@ -80,7 +80,7 @@ public abstract class AbstractFridaCommand<T> implements FridaCommand<T> {
String type = jobj.get("type").getAsString(); String type = jobj.get("type").getAsString();
if (type.equals("error")) { if (type.equals("error")) {
String desc = jobj.get("description").getAsString(); String desc = jobj.get("description").getAsString();
manager.getEventListeners().fire.consoleOutput(desc+"\n", 0); manager.getEventListeners().invoke().consoleOutput(desc+"\n", 0);
Msg.error(this, desc); Msg.error(this, desc);
return; return;
} }
@ -88,14 +88,14 @@ public abstract class AbstractFridaCommand<T> implements FridaCommand<T> {
if (jobj.has("payload")) { if (jobj.has("payload")) {
Object object = jobj.get("payload"); Object object = jobj.get("payload");
if (!(object instanceof JsonPrimitive)) { 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); Msg.error(this, object);
return; return;
} }
String value = ((JsonPrimitive) object).getAsString(); String value = ((JsonPrimitive) object).getAsString();
if (!value.startsWith("{")) { if (!value.startsWith("{")) {
manager.getEventListeners().fire.consoleOutput(object+"\n", 0); manager.getEventListeners().invoke().consoleOutput(object+"\n", 0);
return; return;
} }
JsonElement res = JsonParser.parseString(value); JsonElement res = JsonParser.parseString(value);
@ -106,14 +106,14 @@ public abstract class AbstractFridaCommand<T> implements FridaCommand<T> {
res = keyValue.get("value"); res = keyValue.get("value");
String key = element.getAsString(); String key = element.getAsString();
if (!key.equals(name)) { if (!key.equals(name)) {
manager.getEventListeners().fire.consoleOutput(res+"\n", 0); manager.getEventListeners().invoke().consoleOutput(res+"\n", 0);
return; return;
} }
} else { } else {
manager.getEventListeners().fire.consoleOutput(object+"\n", 0); manager.getEventListeners().invoke().consoleOutput(object+"\n", 0);
} }
} else { } else {
manager.getEventListeners().fire.consoleOutput(object+"\n", 0); manager.getEventListeners().invoke().consoleOutput(object+"\n", 0);
} }
if ("[]".equals(res.toString())) { if ("[]".equals(res.toString())) {
Msg.error(this, "nothing returned for "+this); Msg.error(this, "nothing returned for "+this);

View file

@ -46,7 +46,7 @@ public class FridaDetachCommand extends AbstractFridaCommand<Void> {
for (FridaThread thread : list) { for (FridaThread thread : list) {
manager.removeThread(pid, FridaClient.getId(thread)); manager.removeThread(pid, FridaClient.getId(thread));
} }
manager.getEventListeners().fire.processRemoved(pid, FridaCause.Causes.UNCLAIMED); manager.getEventListeners().invoke().processRemoved(pid, FridaCause.Causes.UNCLAIMED);
return null; return null;
} }

View file

@ -70,7 +70,7 @@ public class FridaManagerImpl implements FridaManager {
private final HandlerMap<FridaEvent<?>, Void, DebugStatus> handlerMap = new HandlerMap<>(); private final HandlerMap<FridaEvent<?>, Void, DebugStatus> handlerMap = new HandlerMap<>();
private final Map<Class<?>, DebugStatus> statusMap = new LinkedHashMap<>(); private final Map<Class<?>, DebugStatus> statusMap = new LinkedHashMap<>();
private final ListenerSet<FridaEventsListener> listenersEvent = private final ListenerSet<FridaEventsListener> listenersEvent =
new ListenerSet<>(FridaEventsListener.class); new ListenerSet<>(FridaEventsListener.class, true);
private FridaTarget currentTarget; private FridaTarget currentTarget;
private FridaSession currentSession; private FridaSession currentSession;
@ -157,7 +157,7 @@ public class FridaManagerImpl implements FridaManager {
for (String tid : toRemove) { for (String tid : toRemove) {
removeThread(processId, tid); 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) { if (sessions.remove(id) == null) {
throw new IllegalArgumentException("There is no session with id " + id); 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) { protected DebugStatus processThreadCreated(FridaThreadCreatedEvent evt, Void v) {
FridaThread thread = evt.getInfo().thread; FridaThread thread = evt.getInfo().thread;
currentThread = thread; currentThread = thread;
getEventListeners().fire.threadCreated(thread, FridaCause.Causes.UNCLAIMED); getEventListeners().invoke().threadCreated(thread, FridaCause.Causes.UNCLAIMED);
getEventListeners().fire.threadSelected(thread, null, evt.getCause()); getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -686,8 +686,8 @@ public class FridaManagerImpl implements FridaManager {
*/ */
protected DebugStatus processThreadReplaced(FridaThreadReplacedEvent evt, Void v) { protected DebugStatus processThreadReplaced(FridaThreadReplacedEvent evt, Void v) {
FridaThread thread = evt.getInfo().thread; FridaThread thread = evt.getInfo().thread;
getEventListeners().fire.threadReplaced(thread, FridaCause.Causes.UNCLAIMED); getEventListeners().invoke().threadReplaced(thread, FridaCause.Causes.UNCLAIMED);
getEventListeners().fire.threadSelected(thread, null, evt.getCause()); getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -699,7 +699,7 @@ public class FridaManagerImpl implements FridaManager {
* @return retval handling/break status * @return retval handling/break status
*/ */
protected DebugStatus processThreadExited(FridaThreadExitedEvent evt, Void v) { 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()); return statusMap.get(evt.getClass());
} }
@ -712,7 +712,7 @@ public class FridaManagerImpl implements FridaManager {
*/ */
protected DebugStatus processThreadSelected(FridaThreadSelectedEvent evt, Void v) { protected DebugStatus processThreadSelected(FridaThreadSelectedEvent evt, Void v) {
currentThread = evt.getThread(); currentThread = evt.getThread();
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause()); getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -725,7 +725,7 @@ public class FridaManagerImpl implements FridaManager {
*/ */
protected DebugStatus processFrameSelected(FridaSelectedFrameChangedEvent evt, Void v) { protected DebugStatus processFrameSelected(FridaSelectedFrameChangedEvent evt, Void v) {
currentThread = evt.getThread(); currentThread = evt.getThread();
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause()); getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -739,8 +739,8 @@ public class FridaManagerImpl implements FridaManager {
protected DebugStatus processProcessCreated(FridaProcessCreatedEvent evt, Void v) { protected DebugStatus processProcessCreated(FridaProcessCreatedEvent evt, Void v) {
FridaProcessInfo info = evt.getInfo(); FridaProcessInfo info = evt.getInfo();
FridaProcess proc = info.process; FridaProcess proc = info.process;
getEventListeners().fire.processAdded(proc, FridaCause.Causes.UNCLAIMED); getEventListeners().invoke().processAdded(proc, FridaCause.Causes.UNCLAIMED);
getEventListeners().fire.processSelected(proc, evt.getCause()); getEventListeners().invoke().processSelected(proc, evt.getCause());
/* /*
FridaThread thread = proc.GetSelectedThread(); FridaThread thread = proc.GetSelectedThread();
@ -759,8 +759,8 @@ public class FridaManagerImpl implements FridaManager {
protected DebugStatus processProcessReplaced(FridaProcessReplacedEvent evt, Void v) { protected DebugStatus processProcessReplaced(FridaProcessReplacedEvent evt, Void v) {
FridaProcessInfo info = evt.getInfo(); FridaProcessInfo info = evt.getInfo();
FridaProcess proc = info.process; FridaProcess proc = info.process;
getEventListeners().fire.processReplaced(proc, FridaCause.Causes.UNCLAIMED); getEventListeners().invoke().processReplaced(proc, FridaCause.Causes.UNCLAIMED);
getEventListeners().fire.processSelected(proc, evt.getCause()); getEventListeners().invoke().processSelected(proc, evt.getCause());
/* /*
FridaThread thread = proc.GetSelectedThread(); FridaThread thread = proc.GetSelectedThread();
@ -779,9 +779,9 @@ public class FridaManagerImpl implements FridaManager {
protected DebugStatus processProcessExited(FridaProcessExitedEvent evt, Void v) { protected DebugStatus processProcessExited(FridaProcessExitedEvent evt, Void v) {
FridaThread thread = getCurrentThread(); FridaThread thread = getCurrentThread();
FridaProcess process = getCurrentProcess(); FridaProcess process = getCurrentProcess();
getEventListeners().fire.threadExited(thread, process, evt.getCause()); getEventListeners().invoke().threadExited(thread, process, evt.getCause());
getEventListeners().fire.processExited(process, evt.getCause()); getEventListeners().invoke().processExited(process, evt.getCause());
getEventListeners().fire.processRemoved(process.getPID().toString(), evt.getCause()); getEventListeners().invoke().processRemoved(process.getPID().toString(), evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -794,7 +794,7 @@ public class FridaManagerImpl implements FridaManager {
*/ */
protected DebugStatus processProcessSelected(FridaProcessSelectedEvent evt, Void v) { protected DebugStatus processProcessSelected(FridaProcessSelectedEvent evt, Void v) {
currentProcess = evt.getProcess(); currentProcess = evt.getProcess();
getEventListeners().fire.processSelected(currentProcess, evt.getCause()); getEventListeners().invoke().processSelected(currentProcess, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -807,8 +807,8 @@ public class FridaManagerImpl implements FridaManager {
*/ */
protected DebugStatus processSessionCreated(FridaSessionCreatedEvent evt, Void v) { protected DebugStatus processSessionCreated(FridaSessionCreatedEvent evt, Void v) {
FridaSessionInfo info = evt.getInfo(); FridaSessionInfo info = evt.getInfo();
getEventListeners().fire.sessionAdded(info.session, FridaCause.Causes.UNCLAIMED); getEventListeners().invoke().sessionAdded(info.session, FridaCause.Causes.UNCLAIMED);
getEventListeners().fire.sessionSelected(info.session, evt.getCause()); getEventListeners().invoke().sessionSelected(info.session, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -821,8 +821,8 @@ public class FridaManagerImpl implements FridaManager {
*/ */
protected DebugStatus processSessionReplaced(FridaSessionReplacedEvent evt, Void v) { protected DebugStatus processSessionReplaced(FridaSessionReplacedEvent evt, Void v) {
FridaSessionInfo info = evt.getInfo(); FridaSessionInfo info = evt.getInfo();
getEventListeners().fire.sessionReplaced(info.session, FridaCause.Causes.UNCLAIMED); getEventListeners().invoke().sessionReplaced(info.session, FridaCause.Causes.UNCLAIMED);
getEventListeners().fire.sessionSelected(info.session, evt.getCause()); getEventListeners().invoke().sessionSelected(info.session, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -835,10 +835,10 @@ public class FridaManagerImpl implements FridaManager {
*/ */
protected DebugStatus processSessionExited(FridaSessionExitedEvent evt, Void v) { protected DebugStatus processSessionExited(FridaSessionExitedEvent evt, Void v) {
removeSession(evt.sessionId, FridaCause.Causes.UNCLAIMED); removeSession(evt.sessionId, FridaCause.Causes.UNCLAIMED);
getEventListeners().fire.sessionRemoved(evt.sessionId, evt.getCause()); getEventListeners().invoke().sessionRemoved(evt.sessionId, evt.getCause());
getEventListeners().fire.threadExited(currentThread, currentProcess, evt.getCause()); getEventListeners().invoke().threadExited(currentThread, currentProcess, evt.getCause());
getEventListeners().fire.processExited(currentProcess, evt.getCause()); getEventListeners().invoke().processExited(currentProcess, evt.getCause());
getEventListeners().fire.processRemoved(currentProcess.getPID().toString(), getEventListeners().invoke().processRemoved(currentProcess.getPID().toString(),
evt.getCause()); evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -855,7 +855,7 @@ public class FridaManagerImpl implements FridaManager {
long n = info.getNumberOfModules(); long n = info.getNumberOfModules();
FridaProcess process = info.getProcess(); FridaProcess process = info.getProcess();
for (int i = 0; i < n; i++) { 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()); return statusMap.get(evt.getClass());
} }
@ -872,7 +872,7 @@ public class FridaManagerImpl implements FridaManager {
long n = info.getNumberOfModules(); long n = info.getNumberOfModules();
FridaProcess process = info.getProcess(); FridaProcess process = info.getProcess();
for (int i = 0; i < n; i++) { 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()); return statusMap.get(evt.getClass());
} }
@ -889,7 +889,7 @@ public class FridaManagerImpl implements FridaManager {
long n = info.getNumberOfModules(); long n = info.getNumberOfModules();
FridaProcess process = info.getProcess(); FridaProcess process = info.getProcess();
for (int i = 0; i < n; i++) { 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()); return statusMap.get(evt.getClass());
} }
@ -906,7 +906,7 @@ public class FridaManagerImpl implements FridaManager {
long n = info.getNumberOfRegions(); long n = info.getNumberOfRegions();
FridaProcess process = info.getProcess(); FridaProcess process = info.getProcess();
for (int i = 0; i < n; i++) { 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()); return statusMap.get(evt.getClass());
} }
@ -923,7 +923,7 @@ public class FridaManagerImpl implements FridaManager {
long n = info.getNumberOfRegions(); long n = info.getNumberOfRegions();
FridaProcess process = info.getProcess(); FridaProcess process = info.getProcess();
for (int i = 0; i < n; i++) { 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()); return statusMap.get(evt.getClass());
} }
@ -940,7 +940,7 @@ public class FridaManagerImpl implements FridaManager {
long n = info.getNumberOfRegions(); long n = info.getNumberOfRegions();
FridaProcess process = info.getProcess(); FridaProcess process = info.getProcess();
for (int i = 0; i < n; i++) { 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()); return statusMap.get(evt.getClass());
} }
@ -1000,13 +1000,13 @@ public class FridaManagerImpl implements FridaManager {
*/ */
protected DebugStatus processSessionSelected(FridaSessionSelectedEvent evt, Void v) { protected DebugStatus processSessionSelected(FridaSessionSelectedEvent evt, Void v) {
FridaSession session = evt.getSession(); FridaSession session = evt.getSession();
getEventListeners().fire.sessionSelected(session, evt.getCause()); getEventListeners().invoke().sessionSelected(session, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
protected void processConsoleOutput(FridaConsoleOutputEvent evt, Void v) { protected void processConsoleOutput(FridaConsoleOutputEvent evt, Void v) {
if (evt.getOutput() != null) { 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) { public CompletableFuture<Void> console(String command) {
if (continuation != null) { if (continuation != null) {
String prompt = command.equals("") ? FridaModelTargetInterpreter.FRIDA_PROMPT : ">>>"; String prompt = command.equals("") ? FridaModelTargetInterpreter.FRIDA_PROMPT : ">>>";
getEventListeners().fire.promptChanged(prompt); getEventListeners().invoke().promptChanged(prompt);
continuation.complete(command); continuation.complete(command);
setContinuation(null); setContinuation(null);
return AsyncUtils.nil(); return AsyncUtils.nil();

View file

@ -98,7 +98,7 @@ public class FridaModelImpl extends AbstractFridaModel implements DebuggerObject
@Override @Override
public void terminate() throws IOException { public void terminate() throws IOException {
listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL);
root.invalidateSubtree(root, "Frida is terminating"); root.invalidateSubtree(root, "Frida is terminating");
manager.terminate(); manager.terminate();
} }

View file

@ -239,11 +239,11 @@ public class GdbManagerImpl implements GdbManager {
Collections.unmodifiableMap(breakpoints); Collections.unmodifiableMap(breakpoints);
protected final ListenerSet<GdbEventsListener> listenersEvent = protected final ListenerSet<GdbEventsListener> listenersEvent =
new ListenerSet<>(GdbEventsListener.class); new ListenerSet<>(GdbEventsListener.class, true);
protected final ListenerSet<GdbTargetOutputListener> listenersTargetOutput = protected final ListenerSet<GdbTargetOutputListener> listenersTargetOutput =
new ListenerSet<>(GdbTargetOutputListener.class); new ListenerSet<>(GdbTargetOutputListener.class, true);
protected final ListenerSet<GdbConsoleOutputListener> listenersConsoleOutput = protected final ListenerSet<GdbConsoleOutputListener> listenersConsoleOutput =
new ListenerSet<>(GdbConsoleOutputListener.class); new ListenerSet<>(GdbConsoleOutputListener.class, true);
protected final ExecutorService eventThread = Executors.newSingleThreadExecutor(); protected final ExecutorService eventThread = Executors.newSingleThreadExecutor();
/** /**
@ -401,7 +401,7 @@ public class GdbManagerImpl implements GdbManager {
@Internal // for detach command @Internal // for detach command
public void fireThreadExited(int tid, GdbInferiorImpl inferior, GdbCause cause) { 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 @Override
@ -474,7 +474,7 @@ public class GdbManagerImpl implements GdbManager {
throw new IllegalArgumentException("There is already inferior " + exists); throw new IllegalArgumentException("There is already inferior " + exists);
} }
inferiors.put(inferior.getId(), inferior); 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) { if (inferiors.remove(iid) == null) {
throw new IllegalArgumentException("There is no inferior with id " + iid); 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) { if (curInferior != inf) {
curInferior = inf; curInferior = inf;
if (fire) { if (fire) {
event(() -> listenersEvent.fire.inferiorSelected(inf, cause), event(() -> listenersEvent.invoke().inferiorSelected(inf, cause),
"updateCurrentInferior"); "updateCurrentInferior");
} }
return true; return true;
@ -1080,7 +1080,7 @@ public class GdbManagerImpl implements GdbManager {
String out = evt.getOutput(); String out = evt.getOutput();
//System.out.print(out); //System.out.print(out);
if (!evt.isStolen()) { if (!evt.isStolen()) {
listenersConsoleOutput.fire.output(Channel.STDOUT, out); listenersConsoleOutput.invoke().output(Channel.STDOUT, out);
} }
if (evt.getInterpreter() == Interpreter.MI2 && if (evt.getInterpreter() == Interpreter.MI2 &&
out.toLowerCase().contains("switching to inferior")) { out.toLowerCase().contains("switching to inferior")) {
@ -1097,7 +1097,7 @@ public class GdbManagerImpl implements GdbManager {
* @param v nothing * @param v nothing
*/ */
protected void processTargetOut(GdbTargetOutputEvent evt, Void v) { 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(); String out = evt.getOutput();
//System.err.print(out); //System.err.print(out);
if (!evt.isStolen()) { 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()); inferior.add(evt.getCause());
if (fireSelected) { if (fireSelected) {
event(() -> listenersEvent.fire.inferiorSelected(inferior, evt.getCause()), event(() -> listenersEvent.invoke().inferiorSelected(inferior, evt.getCause()),
"groupAdded-sel"); "groupAdded-sel");
} }
} }
@ -1161,7 +1161,7 @@ public class GdbManagerImpl implements GdbManager {
} }
inferior.remove(evt.getCause()); inferior.remove(evt.getCause());
if (fireSelected) { if (fireSelected) {
event(() -> listenersEvent.fire.inferiorSelected(cur, evt.getCause()), event(() -> listenersEvent.invoke().inferiorSelected(cur, evt.getCause()),
"groupRemoved-sel"); "groupRemoved-sel");
// Also cause GDB to generate thread selection events, if applicable // Also cause GDB to generate thread selection events, if applicable
setActiveInferior(cur, false); setActiveInferior(cur, false);
@ -1182,7 +1182,7 @@ public class GdbManagerImpl implements GdbManager {
} }
public void fireInferiorStarted(GdbInferiorImpl inf, GdbCause cause, String text) { 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(); int iid = evt.getInferiorId();
GdbInferiorImpl inf = getInferior(iid); GdbInferiorImpl inf = getInferior(iid);
inf.setExitCode(evt.getExitCode()); 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); GdbInferiorImpl inf = getInferior(iid);
GdbThreadImpl thread = new GdbThreadImpl(this, inf, tid); GdbThreadImpl thread = new GdbThreadImpl(this, inf, tid);
thread.add(); 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); GdbInferiorImpl inf = getInferior(iid);
GdbThreadImpl thread = inf.getThread(tid); GdbThreadImpl thread = inf.getThread(tid);
thread.remove(); 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) { public void doThreadSelected(GdbThreadImpl thread, GdbStackFrame frame, GdbCause cause) {
updateCurrentInferior(thread.getInferior(), cause, true); 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 if (iid == null) { // Context of all inferiors
for (GdbInferiorImpl inf : inferiors.values()) { for (GdbInferiorImpl inf : inferiors.values()) {
inf.libraryLoaded(name); inf.libraryLoaded(name);
event(() -> listenersEvent.fire.libraryLoaded(inf, name, evt.getCause()), event(() -> listenersEvent.invoke().libraryLoaded(inf, name, evt.getCause()),
"libraryLoaded"); "libraryLoaded");
} }
} }
else { else {
GdbInferiorImpl inf = getInferior(iid); GdbInferiorImpl inf = getInferior(iid);
inf.libraryLoaded(name); inf.libraryLoaded(name);
event(() -> listenersEvent.fire.libraryLoaded(inf, name, evt.getCause()), event(() -> listenersEvent.invoke().libraryLoaded(inf, name, evt.getCause()),
"libraryLoaded"); "libraryLoaded");
} }
} }
@ -1289,14 +1289,14 @@ public class GdbManagerImpl implements GdbManager {
if (iid == null) { // Context of all inferiors if (iid == null) { // Context of all inferiors
for (GdbInferiorImpl inf : inferiors.values()) { for (GdbInferiorImpl inf : inferiors.values()) {
inf.libraryUnloaded(name); inf.libraryUnloaded(name);
event(() -> listenersEvent.fire.libraryUnloaded(inf, name, evt.getCause()), event(() -> listenersEvent.invoke().libraryUnloaded(inf, name, evt.getCause()),
"libraryUnloaded"); "libraryUnloaded");
} }
} }
else { else {
GdbInferiorImpl inf = getInferior(iid); GdbInferiorImpl inf = getInferior(iid);
inf.libraryUnloaded(name); inf.libraryUnloaded(name);
event(() -> listenersEvent.fire.libraryUnloaded(inf, name, evt.getCause()), event(() -> listenersEvent.invoke().libraryUnloaded(inf, name, evt.getCause()),
"libraryUnloaded"); "libraryUnloaded");
} }
} }
@ -1310,7 +1310,7 @@ public class GdbManagerImpl implements GdbManager {
@Internal @Internal
public void doBreakpointCreated(GdbBreakpointInfo newInfo, GdbCause cause) { public void doBreakpointCreated(GdbBreakpointInfo newInfo, GdbCause cause) {
addKnownBreakpoint(newInfo, false); 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 @Internal
public void doBreakpointModified(GdbBreakpointInfo newInfo, GdbCause cause) { public void doBreakpointModified(GdbBreakpointInfo newInfo, GdbCause cause) {
GdbBreakpointInfo oldInfo = addKnownBreakpoint(newInfo, true); GdbBreakpointInfo oldInfo = addKnownBreakpoint(newInfo, true);
event(() -> listenersEvent.fire.breakpointModified(newInfo, oldInfo, cause), event(() -> listenersEvent.invoke().breakpointModified(newInfo, oldInfo, cause),
"breakpointModified"); "breakpointModified");
} }
@ -1358,7 +1358,7 @@ public class GdbManagerImpl implements GdbManager {
if (oldInfo == null) { if (oldInfo == null) {
return; return;
} }
event(() -> listenersEvent.fire.breakpointDeleted(oldInfo, cause), "breakpointDeleted"); event(() -> listenersEvent.invoke().breakpointDeleted(oldInfo, cause), "breakpointDeleted");
} }
protected void doBreakpointModifiedSameLocations(GdbBreakpointInfo newInfo, protected void doBreakpointModifiedSameLocations(GdbBreakpointInfo newInfo,
@ -1367,7 +1367,7 @@ public class GdbManagerImpl implements GdbManager {
return; return;
} }
addKnownBreakpoint(newInfo, true); addKnownBreakpoint(newInfo, true);
event(() -> listenersEvent.fire.breakpointModified(newInfo, oldInfo, cause), event(() -> listenersEvent.invoke().breakpointModified(newInfo, oldInfo, cause),
"breakpointModified"); "breakpointModified");
} }
@ -1412,7 +1412,7 @@ public class GdbManagerImpl implements GdbManager {
protected void processMemoryChanged(GdbMemoryChangedEvent evt, Void v) { protected void processMemoryChanged(GdbMemoryChangedEvent evt, Void v) {
int iid = evt.getInferiorId(); int iid = evt.getInferiorId();
GdbInferior inf = getInferior(iid); 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"); evt.getCause()), "memoryChanged");
} }
@ -1423,7 +1423,7 @@ public class GdbManagerImpl implements GdbManager {
* @param v nothing * @param v nothing
*/ */
protected void processParamChanged(GdbParamChangedEvent evt, Void v) { 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"); evt.getCause()), "paramChanged");
} }
@ -1467,7 +1467,7 @@ public class GdbManagerImpl implements GdbManager {
GdbMiFieldList newFrame = evt.checkFrame(); GdbMiFieldList newFrame = evt.checkFrame();
GdbStackFrameImpl frame = GdbStackFrameImpl frame =
newFrame == null ? null : GdbStackFrameImpl.fromFieldList(thread, newFrame); 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)) { if ("all".equals(threadId)) {
GdbInferiorImpl cur = curInferior; GdbInferiorImpl cur = curInferior;
event(() -> { event(() -> {
listenersEvent.fire.inferiorStateChanged(cur, cur.getKnownThreads().values(), listenersEvent.invoke().inferiorStateChanged(cur, cur.getKnownThreads().values(),
evt.newState(), null, evt.getCause(), evt.getReason()); evt.newState(), null, evt.getCause(), evt.getReason());
}, "inferiorState-running"); }, "inferiorState-running");
for (GdbThreadImpl thread : curInferior.getKnownThreadsImpl().values()) { for (GdbThreadImpl thread : curInferior.getKnownThreadsImpl().values()) {
@ -1549,7 +1549,7 @@ public class GdbManagerImpl implements GdbManager {
int id = Integer.parseUnsignedInt(threadId); int id = Integer.parseUnsignedInt(threadId);
GdbThreadImpl thread = threads.get(id); GdbThreadImpl thread = threads.get(id);
event(() -> { event(() -> {
listenersEvent.fire.inferiorStateChanged(thread.getInferior(), listenersEvent.invoke().inferiorStateChanged(thread.getInferior(),
List.of(thread), evt.newState(), null, evt.getCause(), evt.getReason()); List.of(thread), evt.newState(), null, evt.getCause(), evt.getReason());
}, "inferiorState-running"); }, "inferiorState-running");
thread.setState(evt.newState(), evt.getCause(), evt.getReason()); 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()) { for (Map.Entry<GdbInferior, Set<GdbThread>> ent : byInf.entrySet()) {
event(() -> { event(() -> {
listenersEvent.fire.inferiorStateChanged(ent.getKey(), ent.getValue(), listenersEvent.invoke().inferiorStateChanged(ent.getKey(), ent.getValue(),
evt.newState(), evtThread, evt.getCause(), evt.getReason()); evt.newState(), evtThread, evt.getCause(), evt.getReason());
}, "inferiorState-stopped"); }, "inferiorState-stopped");
} }
if (evtThread != null) { if (evtThread != null) {
GdbStackFrameImpl frame = evt.getFrame(evtThread); GdbStackFrameImpl frame = evt.getFrame(evtThread);
event(() -> listenersEvent.fire.threadSelected(evtThread, frame, evt), event(() -> listenersEvent.invoke().threadSelected(evtThread, frame, evt),
"inferiorState-stopped"); "inferiorState-stopped");
} }
} }
@ -1701,7 +1701,7 @@ public class GdbManagerImpl implements GdbManager {
@Internal @Internal
public void synthesizeConsoleOut(Channel channel, String line) { public void synthesizeConsoleOut(Channel channel, String line) {
listenersConsoleOutput.fire.output(channel, line); listenersConsoleOutput.invoke().output(channel, line);
} }
@Override @Override

View file

@ -81,7 +81,7 @@ public class GdbThreadImpl implements GdbThread {
this.inferior.addThread(this); this.inferior.addThread(this);
this.manager.addThread(this); this.manager.addThread(this);
state.addChangeListener((oldState, newState, pair) -> { 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"); pair.cause, pair.reason), "threadState");
}); });
} }

View file

@ -167,7 +167,7 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel {
} }
public void terminate() throws IOException { public void terminate() throws IOException {
listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL);
session.invalidateSubtree(session, "GDB is terminating"); session.invalidateSubtree(session, "GDB is terminating");
gdb.terminate(); gdb.terminate();
} }

View file

@ -15,7 +15,8 @@
*/ */
package agent.gdb.model.impl; package agent.gdb.model.impl;
import java.util.*; import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -30,15 +31,11 @@ import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.target.schema.TargetObjectSchemaInfo; import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.ListenerSet;
import ghidra.util.datastruct.WeakValueHashMap; import ghidra.util.datastruct.WeakValueHashMap;
@TargetObjectSchemaInfo( @TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = {
name = "BreakpointSpec", @TargetAttributeType(type = Void.class) }, canonicalContainer = true)
attributes = {
@TargetAttributeType(type = Void.class) },
canonicalContainer = true)
public class GdbModelTargetBreakpointSpec extends public class GdbModelTargetBreakpointSpec extends
DefaultTargetObject<GdbModelTargetBreakpointLocation, GdbModelTargetBreakpointContainer> DefaultTargetObject<GdbModelTargetBreakpointLocation, GdbModelTargetBreakpointContainer>
implements TargetBreakpointSpec, TargetDeletable { implements TargetBreakpointSpec, TargetDeletable {
@ -62,12 +59,7 @@ public class GdbModelTargetBreakpointSpec extends
protected final Map<Long, GdbModelTargetBreakpointLocation> breaksBySub = protected final Map<Long, GdbModelTargetBreakpointLocation> breaksBySub =
new WeakValueHashMap<>(); new WeakValueHashMap<>();
protected final ListenerSet<TargetBreakpointAction> actions = protected final ListenerSet<TargetBreakpointAction> actions =
new ListenerSet<>(TargetBreakpointAction.class) { new ListenerSet<>(TargetBreakpointAction.class, false);
// Use strong references on actions
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
return new LinkedHashMap<>();
};
};
public GdbModelTargetBreakpointSpec(GdbModelTargetBreakpointContainer breakpoints, public GdbModelTargetBreakpointSpec(GdbModelTargetBreakpointContainer breakpoints,
GdbBreakpointInfo info) { GdbBreakpointInfo info) {
@ -171,13 +163,12 @@ public class GdbModelTargetBreakpointSpec extends
} }
protected void updateAttributesFromInfo(String reason) { protected void updateAttributesFromInfo(String reason) {
changeAttributes(List.of(), Map.of( changeAttributes(List.of(),
ENABLED_ATTRIBUTE_NAME, enabled = info.isEnabled(), Map.of(ENABLED_ATTRIBUTE_NAME, enabled = info.isEnabled(), EXPRESSION_ATTRIBUTE_NAME,
EXPRESSION_ATTRIBUTE_NAME,
expression = info.getType() == GdbBreakpointType.CATCHPOINT ? info.getCatchType() expression = info.getType() == GdbBreakpointType.CATCHPOINT ? info.getCatchType()
: info.getOriginalLocation(), : info.getOriginalLocation(),
KINDS_ATTRIBUTE_NAME, kinds = computeKinds(info), KINDS_ATTRIBUTE_NAME, kinds = computeKinds(info), DISPLAY_ATTRIBUTE_NAME,
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay()), display = computeDisplay()),
reason); reason);
} }
@ -232,7 +223,7 @@ public class GdbModelTargetBreakpointSpec extends
protected void breakpointHit(GdbModelTargetStackFrame frame, protected void breakpointHit(GdbModelTargetStackFrame frame,
GdbModelTargetBreakpointLocation eb) { GdbModelTargetBreakpointLocation eb) {
actions.fire.breakpointHit(this, frame.thread, frame, eb); actions.invoke().breakpointHit(this, frame.thread, frame, eb);
} }
public synchronized GdbModelTargetBreakpointLocation getTargetBreakpointLocation( public synchronized GdbModelTargetBreakpointLocation getTargetBreakpointLocation(
@ -270,15 +261,21 @@ public class GdbModelTargetBreakpointSpec extends
case BREAKPOINT: case BREAKPOINT:
case HW_BREAKPOINT: case HW_BREAKPOINT:
case OTHER: case OTHER:
return String.format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(), return String
info.getDisp(), enb, addr, what).trim(); .format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(),
info.getDisp(), enb, addr, what)
.trim();
case CATCHPOINT: case CATCHPOINT:
return String.format("%d %s %s %s %s", info.getNumber(), info.getTypeName(), return String
info.getDisp(), enb, what).trim(); .format("%d %s %s %s %s", info.getNumber(), info.getTypeName(),
info.getDisp(), enb, what)
.trim();
case DPRINTF: case DPRINTF:
// TODO: script? // TODO: script?
return String.format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(), return String
info.getDisp(), enb, addr, what).trim(); .format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(),
info.getDisp(), enb, addr, what)
.trim();
} }
throw new AssertionError(); throw new AssertionError();
} }

View file

@ -39,7 +39,7 @@ public class LldbDetachCommand extends AbstractLldbCommand<Void> {
SBThread t = process.GetThreadAtIndex(i); SBThread t = process.GetThreadAtIndex(i);
manager.removeThread(pid, DebugClient.getId(t)); manager.removeThread(pid, DebugClient.getId(t));
} }
manager.getEventListeners().fire.processRemoved(pid, LldbCause.Causes.UNCLAIMED); manager.getEventListeners().invoke().processRemoved(pid, LldbCause.Causes.UNCLAIMED);
return null; return null;
} }

View file

@ -94,7 +94,7 @@ public class LldbManagerImpl implements LldbManager {
private final HandlerMap<LldbEvent<?>, Void, DebugStatus> handlerMap = new HandlerMap<>(); private final HandlerMap<LldbEvent<?>, Void, DebugStatus> handlerMap = new HandlerMap<>();
private final Map<Class<?>, DebugStatus> statusMap = new LinkedHashMap<>(); private final Map<Class<?>, DebugStatus> statusMap = new LinkedHashMap<>();
private final ListenerSet<LldbEventsListener> listenersEvent = private final ListenerSet<LldbEventsListener> listenersEvent =
new ListenerSet<>(LldbEventsListener.class); new ListenerSet<>(LldbEventsListener.class, true);
private SBEvent currentEvent; private SBEvent currentEvent;
private SBTarget currentSession; private SBTarget currentSession;
@ -136,8 +136,9 @@ public class LldbManagerImpl implements LldbManager {
public void addThreadIfAbsent(SBProcess process, SBThread thread) { public void addThreadIfAbsent(SBProcess process, SBThread thread) {
synchronized (threads) { synchronized (threads) {
if (!process.IsValid()) if (!process.IsValid()) {
return; return;
}
Map<String, SBThread> map = threads.get(DebugClient.getId(process)); Map<String, SBThread> map = threads.get(DebugClient.getId(process));
if (map == null) { if (map == null) {
map = new HashMap<>(); map = new HashMap<>();
@ -188,18 +189,10 @@ public class LldbManagerImpl implements LldbManager {
for (String tid : toRemove) { for (String tid : toRemove) {
removeThread(processId, tid); 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 @Override
public SBProcess getProcess(SBTarget session, String id) { public SBProcess getProcess(SBTarget session, String id) {
synchronized (processes) { synchronized (processes) {
@ -214,8 +207,9 @@ public class LldbManagerImpl implements LldbManager {
public void addProcessIfAbsent(SBTarget session, SBProcess process) { public void addProcessIfAbsent(SBTarget session, SBProcess process) {
synchronized (processes) { synchronized (processes) {
if (!session.IsValid()) if (!session.IsValid()) {
return; return;
}
String sessionId = DebugClient.getId(session); String sessionId = DebugClient.getId(session);
Map<String, SBProcess> map = processes.get(sessionId); Map<String, SBProcess> map = processes.get(sessionId);
if (map == null) { if (map == null) {
@ -252,7 +246,7 @@ public class LldbManagerImpl implements LldbManager {
if (sessions.remove(id) == null) { if (sessions.remove(id) == null) {
throw new IllegalArgumentException("There is no session with id " + id); throw new IllegalArgumentException("There is no session with id " + id);
} }
getEventListeners().fire.sessionRemoved(id, cause); getEventListeners().invoke().sessionRemoved(id, cause);
} }
} }
@ -310,8 +304,9 @@ public class LldbManagerImpl implements LldbManager {
public void addModuleIfAbsent(SBTarget session, SBModule module) { public void addModuleIfAbsent(SBTarget session, SBModule module) {
synchronized (modules) { synchronized (modules) {
if (!session.IsValid()) if (!session.IsValid()) {
return; return;
}
String sessionId = DebugClient.getId(session); String sessionId = DebugClient.getId(session);
Map<String, SBModule> map = modules.get(sessionId); Map<String, SBModule> map = modules.get(sessionId);
if (map == null) { if (map == null) {
@ -347,8 +342,9 @@ public class LldbManagerImpl implements LldbManager {
public void addBreakpointIfAbsent(SBTarget session, Object bpt) { public void addBreakpointIfAbsent(SBTarget session, Object bpt) {
synchronized (breakpoints) { synchronized (breakpoints) {
if (!session.IsValid()) if (!session.IsValid()) {
return; return;
}
String sessionId = DebugClient.getId(session); String sessionId = DebugClient.getId(session);
Map<String, Object> map = breakpoints.get(sessionId); Map<String, Object> map = breakpoints.get(sessionId);
if (map == null) { if (map == null) {
@ -484,13 +480,11 @@ public class LldbManagerImpl implements LldbManager {
state.set(null, Causes.UNCLAIMED); state.set(null, Causes.UNCLAIMED);
boolean create = true; boolean create = true;
if (args.length == 0) { if (args.length == 0) {
executor = executor = new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
} }
else { else {
// TODO - process args // TODO - process args
executor = executor = new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
create = false; create = false;
} }
executor.setManager(this); executor.setManager(this);
@ -741,16 +735,14 @@ public class LldbManagerImpl implements LldbManager {
@Override @Override
public void updateState(SBProcess process) { public void updateState(SBProcess process) {
currentProcess = eventProcess = process; currentProcess = eventProcess = process;
if (currentSession == null || if (currentSession == null || !currentSession.IsValid() ||
!currentSession.IsValid() ||
!currentSession.equals(process.GetTarget())) { !currentSession.equals(process.GetTarget())) {
SBTarget candidateSession = currentProcess.GetTarget(); SBTarget candidateSession = currentProcess.GetTarget();
if (candidateSession != null && candidateSession.IsValid()) { if (candidateSession != null && candidateSession.IsValid()) {
currentSession = eventSession = candidateSession; currentSession = eventSession = candidateSession;
} }
} }
if (currentThread == null || if (currentThread == null || !currentThread.IsValid() ||
!currentThread.IsValid() ||
!currentThread.equals(process.GetSelectedThread())) { !currentThread.equals(process.GetSelectedThread())) {
SBThread candidateThread = currentProcess.GetSelectedThread(); SBThread candidateThread = currentProcess.GetSelectedThread();
if (candidateThread != null && candidateThread.IsValid()) { if (candidateThread != null && candidateThread.IsValid()) {
@ -785,7 +777,7 @@ public class LldbManagerImpl implements LldbManager {
for (int i = 0; i < currentSession.GetNumBreakpoints(); i++) { for (int i = 0; i < currentSession.GetNumBreakpoints(); i++) {
SBBreakpoint bpt = currentSession.GetBreakpointAtIndex(i); SBBreakpoint bpt = currentSession.GetBreakpointAtIndex(i);
if (bpt.IsValid() && (bpt.GetID() == id.intValue())) { if (bpt.IsValid() && (bpt.GetID() == id.intValue())) {
getEventListeners().fire.breakpointHit(bpt, evt.getCause()); getEventListeners().invoke().breakpointHit(bpt, evt.getCause());
} }
} }
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
@ -831,8 +823,8 @@ public class LldbManagerImpl implements LldbManager {
*/ */
protected DebugStatus processThreadCreated(LldbThreadCreatedEvent evt, Void v) { protected DebugStatus processThreadCreated(LldbThreadCreatedEvent evt, Void v) {
SBThread thread = evt.getInfo().thread; SBThread thread = evt.getInfo().thread;
getEventListeners().fire.threadCreated(thread, LldbCause.Causes.UNCLAIMED); getEventListeners().invoke().threadCreated(thread, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.threadSelected(thread, null, evt.getCause()); getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -845,7 +837,7 @@ public class LldbManagerImpl implements LldbManager {
*/ */
protected DebugStatus processThreadReplaced(LldbThreadReplacedEvent evt, Void v) { protected DebugStatus processThreadReplaced(LldbThreadReplacedEvent evt, Void v) {
SBThread thread = evt.getInfo().thread; SBThread thread = evt.getInfo().thread;
getEventListeners().fire.threadSelected(thread, null, evt.getCause()); getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -857,7 +849,7 @@ public class LldbManagerImpl implements LldbManager {
* @return retval handling/break status * @return retval handling/break status
*/ */
protected DebugStatus processThreadExited(LldbThreadExitedEvent evt, Void v) { 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()); return statusMap.get(evt.getClass());
} }
@ -870,7 +862,7 @@ public class LldbManagerImpl implements LldbManager {
*/ */
protected DebugStatus processThreadSelected(LldbThreadSelectedEvent evt, Void v) { protected DebugStatus processThreadSelected(LldbThreadSelectedEvent evt, Void v) {
currentThread = evt.getThread(); currentThread = evt.getThread();
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause()); getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -883,7 +875,7 @@ public class LldbManagerImpl implements LldbManager {
*/ */
protected DebugStatus processFrameSelected(LldbSelectedFrameChangedEvent evt, Void v) { protected DebugStatus processFrameSelected(LldbSelectedFrameChangedEvent evt, Void v) {
currentThread = evt.getThread(); currentThread = evt.getThread();
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause()); getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -897,11 +889,11 @@ public class LldbManagerImpl implements LldbManager {
protected DebugStatus processProcessCreated(LldbProcessCreatedEvent evt, Void v) { protected DebugStatus processProcessCreated(LldbProcessCreatedEvent evt, Void v) {
DebugProcessInfo info = evt.getInfo(); DebugProcessInfo info = evt.getInfo();
SBProcess proc = info.process; SBProcess proc = info.process;
getEventListeners().fire.processAdded(proc, LldbCause.Causes.UNCLAIMED); getEventListeners().invoke().processAdded(proc, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.processSelected(proc, evt.getCause()); getEventListeners().invoke().processSelected(proc, evt.getCause());
SBThread thread = proc.GetSelectedThread(); SBThread thread = proc.GetSelectedThread();
getEventListeners().fire.threadSelected(thread, null, evt.getCause()); getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -915,11 +907,11 @@ public class LldbManagerImpl implements LldbManager {
protected DebugStatus processProcessReplaced(LldbProcessReplacedEvent evt, Void v) { protected DebugStatus processProcessReplaced(LldbProcessReplacedEvent evt, Void v) {
DebugProcessInfo info = evt.getInfo(); DebugProcessInfo info = evt.getInfo();
SBProcess proc = info.process; SBProcess proc = info.process;
getEventListeners().fire.processReplaced(proc, LldbCause.Causes.UNCLAIMED); getEventListeners().invoke().processReplaced(proc, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.processSelected(proc, evt.getCause()); getEventListeners().invoke().processSelected(proc, evt.getCause());
SBThread thread = proc.GetSelectedThread(); SBThread thread = proc.GetSelectedThread();
getEventListeners().fire.threadSelected(thread, null, evt.getCause()); getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -933,9 +925,10 @@ public class LldbManagerImpl implements LldbManager {
protected DebugStatus processProcessExited(LldbProcessExitedEvent evt, Void v) { protected DebugStatus processProcessExited(LldbProcessExitedEvent evt, Void v) {
SBThread thread = getCurrentThread(); SBThread thread = getCurrentThread();
SBProcess process = getCurrentProcess(); SBProcess process = getCurrentProcess();
getEventListeners().fire.threadExited(thread, process, evt.getCause()); getEventListeners().invoke().threadExited(thread, process, evt.getCause());
getEventListeners().fire.processExited(process, evt.getCause()); getEventListeners().invoke().processExited(process, evt.getCause());
getEventListeners().fire.processRemoved(process.GetProcessID().toString(), evt.getCause()); getEventListeners().invoke()
.processRemoved(process.GetProcessID().toString(), evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -948,7 +941,7 @@ public class LldbManagerImpl implements LldbManager {
*/ */
protected DebugStatus processProcessSelected(LldbProcessSelectedEvent evt, Void v) { protected DebugStatus processProcessSelected(LldbProcessSelectedEvent evt, Void v) {
currentProcess = evt.getProcess(); currentProcess = evt.getProcess();
getEventListeners().fire.processSelected(currentProcess, evt.getCause()); getEventListeners().invoke().processSelected(currentProcess, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -961,8 +954,8 @@ public class LldbManagerImpl implements LldbManager {
*/ */
protected DebugStatus processSessionCreated(LldbSessionCreatedEvent evt, Void v) { protected DebugStatus processSessionCreated(LldbSessionCreatedEvent evt, Void v) {
DebugSessionInfo info = evt.getInfo(); DebugSessionInfo info = evt.getInfo();
getEventListeners().fire.sessionAdded(info.session, LldbCause.Causes.UNCLAIMED); getEventListeners().invoke().sessionAdded(info.session, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.sessionSelected(info.session, evt.getCause()); getEventListeners().invoke().sessionSelected(info.session, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -975,8 +968,8 @@ public class LldbManagerImpl implements LldbManager {
*/ */
protected DebugStatus processSessionReplaced(LldbSessionReplacedEvent evt, Void v) { protected DebugStatus processSessionReplaced(LldbSessionReplacedEvent evt, Void v) {
DebugSessionInfo info = evt.getInfo(); DebugSessionInfo info = evt.getInfo();
getEventListeners().fire.sessionReplaced(info.session, LldbCause.Causes.UNCLAIMED); getEventListeners().invoke().sessionReplaced(info.session, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.sessionSelected(info.session, evt.getCause()); getEventListeners().invoke().sessionSelected(info.session, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -989,11 +982,11 @@ public class LldbManagerImpl implements LldbManager {
*/ */
protected DebugStatus processSessionExited(LldbSessionExitedEvent evt, Void v) { protected DebugStatus processSessionExited(LldbSessionExitedEvent evt, Void v) {
removeSession(evt.sessionId, LldbCause.Causes.UNCLAIMED); removeSession(evt.sessionId, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.sessionRemoved(evt.sessionId, evt.getCause()); getEventListeners().invoke().sessionRemoved(evt.sessionId, evt.getCause());
getEventListeners().fire.threadExited(eventThread, eventProcess, evt.getCause()); getEventListeners().invoke().threadExited(eventThread, eventProcess, evt.getCause());
getEventListeners().fire.processExited(eventProcess, evt.getCause()); getEventListeners().invoke().processExited(eventProcess, evt.getCause());
getEventListeners().fire.processRemoved(eventProcess.GetProcessID().toString(), getEventListeners().invoke()
evt.getCause()); .processRemoved(eventProcess.GetProcessID().toString(), evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -1009,7 +1002,7 @@ public class LldbManagerImpl implements LldbManager {
long n = info.getNumberOfModules(); long n = info.getNumberOfModules();
SBProcess process = info.getProcess(); SBProcess process = info.getProcess();
for (int i = 0; i < n; i++) { 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()); return statusMap.get(evt.getClass());
} }
@ -1026,7 +1019,7 @@ public class LldbManagerImpl implements LldbManager {
long n = info.getNumberOfModules(); long n = info.getNumberOfModules();
SBProcess process = info.getProcess(); SBProcess process = info.getProcess();
for (int i = 0; i < n; i++) { 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()); return statusMap.get(evt.getClass());
} }
@ -1099,7 +1092,7 @@ public class LldbManagerImpl implements LldbManager {
*/ */
protected DebugStatus processSessionSelected(LldbSessionSelectedEvent evt, Void v) { protected DebugStatus processSessionSelected(LldbSessionSelectedEvent evt, Void v) {
SBTarget session = evt.getSession(); SBTarget session = evt.getSession();
getEventListeners().fire.sessionSelected(session, evt.getCause()); getEventListeners().invoke().sessionSelected(session, evt.getCause());
return statusMap.get(evt.getClass()); return statusMap.get(evt.getClass());
} }
@ -1116,7 +1109,7 @@ public class LldbManagerImpl implements LldbManager {
protected void processConsoleOutput(LldbConsoleOutputEvent evt, Void v) { protected void processConsoleOutput(LldbConsoleOutputEvent evt, Void v) {
if (evt.getOutput() != null) { if (evt.getOutput() != null) {
getEventListeners().fire.consoleOutput(evt.getOutput(), evt.getMask()); getEventListeners().invoke().consoleOutput(evt.getOutput(), evt.getMask());
} }
} }
@ -1208,8 +1201,7 @@ public class LldbManagerImpl implements LldbManager {
* @param evt the event * @param evt the event
* @param v nothing * @param v nothing
*/ */
protected void processBreakpointLocationsAdded(LldbBreakpointLocationsAddedEvent evt, protected void processBreakpointLocationsAdded(LldbBreakpointLocationsAddedEvent evt, Void v) {
Void v) {
SBTarget session = getCurrentSession(); SBTarget session = getCurrentSession();
Object info = evt.getBreakpointInfo(); Object info = evt.getBreakpointInfo();
doBreakpointModified(session, info, evt.getCause()); doBreakpointModified(session, info, evt.getCause());
@ -1311,7 +1303,7 @@ public class LldbManagerImpl implements LldbManager {
@Internal @Internal
public void doBreakpointCreated(SBTarget session, Object info, LldbCause cause) { public void doBreakpointCreated(SBTarget session, Object info, LldbCause cause) {
addKnownBreakpoint(session, info, false); addKnownBreakpoint(session, info, false);
getEventListeners().fire.breakpointCreated(info, cause); getEventListeners().invoke().breakpointCreated(info, cause);
} }
/** /**
@ -1323,7 +1315,7 @@ public class LldbManagerImpl implements LldbManager {
@Internal @Internal
public void doBreakpointModified(SBTarget session, Object info, LldbCause cause) { public void doBreakpointModified(SBTarget session, Object info, LldbCause cause) {
addKnownBreakpoint(session, info, true); addKnownBreakpoint(session, info, true);
getEventListeners().fire.breakpointModified(info, cause); getEventListeners().invoke().breakpointModified(info, cause);
} }
/** /**
@ -1338,13 +1330,13 @@ public class LldbManagerImpl implements LldbManager {
if (oldInfo == null) { if (oldInfo == null) {
return; return;
} }
getEventListeners().fire.breakpointDeleted(oldInfo, cause); getEventListeners().invoke().breakpointDeleted(oldInfo, cause);
} }
protected void doBreakpointModifiedSameLocations(SBTarget session, Object info, protected void doBreakpointModifiedSameLocations(SBTarget session, Object info,
LldbCause cause) { LldbCause cause) {
addKnownBreakpoint(session, info, true); addKnownBreakpoint(session, info, true);
getEventListeners().fire.breakpointModified(info, cause); getEventListeners().invoke().breakpointModified(info, cause);
} }
@Internal @Internal
@ -1572,7 +1564,7 @@ public class LldbManagerImpl implements LldbManager {
for (int i = 0; i < currentProcess.GetNumThreads(); i++) { for (int i = 0; i < currentProcess.GetNumThreads(); i++) {
SBThread thread = currentProcess.GetThreadAtIndex(i); SBThread thread = currentProcess.GetThreadAtIndex(i);
if (thread.IsValid()) { if (thread.IsValid()) {
Msg.warn(this, "defaulting to thread "+i); Msg.warn(this, "defaulting to thread " + i);
currentThread = thread; currentThread = thread;
break; break;
} }
@ -1638,7 +1630,7 @@ public class LldbManagerImpl implements LldbManager {
public CompletableFuture<Void> console(String command) { public CompletableFuture<Void> console(String command) {
if (continuation != null) { if (continuation != null) {
String prompt = command.equals("") ? LldbModelTargetInterpreter.LLDB_PROMPT : ">>>"; String prompt = command.equals("") ? LldbModelTargetInterpreter.LLDB_PROMPT : ">>>";
getEventListeners().fire.promptChanged(prompt); getEventListeners().invoke().promptChanged(prompt);
continuation.complete(command); continuation.complete(command);
setContinuation(null); setContinuation(null);
return AsyncUtils.nil(); return AsyncUtils.nil();

View file

@ -88,7 +88,7 @@ public interface LldbModelTargetBreakpointSpec extends //
public default void breakpointHit() { public default void breakpointHit() {
LldbModelTargetThread targetThread = LldbModelTargetThread targetThread =
getParentProcess().getThreads().getTargetThread(getManager().getEventThread()); getParentProcess().getThreads().getTargetThread(getManager().getEventThread());
getActions().fire.breakpointHit((LldbModelTargetBreakpointSpec) getProxy(), targetThread, getActions().invoke().breakpointHit((LldbModelTargetBreakpointSpec) getProxy(), targetThread,
null, findLocation(targetThread)); null, findLocation(targetThread));
} }

View file

@ -110,7 +110,7 @@ public class LldbModelImpl extends AbstractLldbModel implements DebuggerObjectMo
@Override @Override
public void terminate() throws IOException { public void terminate() throws IOException {
listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL);
root.invalidateSubtree(root, "LLDB is terminating"); root.invalidateSubtree(root, "LLDB is terminating");
manager.terminate(); manager.terminate();
} }

View file

@ -15,7 +15,8 @@
*/ */
package agent.lldb.model.impl; package agent.lldb.model.impl;
import java.util.*; import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import SWIG.SBBreakpointLocation; import SWIG.SBBreakpointLocation;
@ -27,23 +28,16 @@ import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet; import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.ListenerSet;
import ghidra.util.datastruct.WeakValueHashMap; import ghidra.util.datastruct.WeakValueHashMap;
@TargetObjectSchemaInfo( @TargetObjectSchemaInfo(name = "BreakpointSpec", elements = { //
name = "BreakpointSpec", @TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class) }, attributes = {
elements = { //
@TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class)
},
attributes = {
@TargetAttributeType(name = "Type", type = String.class), @TargetAttributeType(name = "Type", type = String.class),
@TargetAttributeType(name = "Valid", type = Boolean.class), @TargetAttributeType(name = "Valid", type = Boolean.class),
@TargetAttributeType(name = "Enabled", type = Boolean.class), @TargetAttributeType(name = "Enabled", type = Boolean.class),
@TargetAttributeType(name = "Count", type = Long.class), @TargetAttributeType(name = "Count", type = Long.class),
@TargetAttributeType(type = Void.class) @TargetAttributeType(type = Void.class) }, canonicalContainer = true)
},
canonicalContainer = true)
public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetObjectImpl public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetObjectImpl
implements LldbModelTargetBreakpointSpec { implements LldbModelTargetBreakpointSpec {
@ -60,12 +54,7 @@ public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetO
protected final Map<Integer, LldbModelTargetBreakpointLocation> breaksBySub = protected final Map<Integer, LldbModelTargetBreakpointLocation> breaksBySub =
new WeakValueHashMap<>(); new WeakValueHashMap<>();
protected final ListenerSet<TargetBreakpointAction> actions = protected final ListenerSet<TargetBreakpointAction> actions =
new ListenerSet<>(TargetBreakpointAction.class) { new ListenerSet<>(TargetBreakpointAction.class, false);
// Use strong references on actions
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
return new LinkedHashMap<>();
};
};
public LldbModelTargetAbstractXpointSpec(LldbModelTargetBreakpointContainer breakpoints, public LldbModelTargetAbstractXpointSpec(LldbModelTargetBreakpointContainer breakpoints,
Object info, String title) { Object info, String title) {
@ -159,7 +148,7 @@ public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetO
protected void breakpointHit(LldbModelTargetStackFrame frame, protected void breakpointHit(LldbModelTargetStackFrame frame,
LldbModelTargetBreakpointLocation eb) { LldbModelTargetBreakpointLocation eb) {
actions.fire.breakpointHit(this, frame.getParentThread(), frame, eb); actions.invoke().breakpointHit(this, frame.getParentThread(), frame, eb);
} }
public synchronized LldbModelTargetBreakpointLocation getTargetBreakpointLocation( public synchronized LldbModelTargetBreakpointLocation getTargetBreakpointLocation(
@ -180,7 +169,7 @@ public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetO
@Override @Override
public ListenerSet<TargetBreakpointAction> getActions() { public ListenerSet<TargetBreakpointAction> getActions() {
return new ListenerSet<TargetBreakpointAction>(null); return new ListenerSet<TargetBreakpointAction>(null, true);
} }
} }

View file

@ -16,7 +16,8 @@
package agent.lldb.model.impl; package agent.lldb.model.impl;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import SWIG.SBBreakpoint; import SWIG.SBBreakpoint;
@ -27,31 +28,19 @@ import ghidra.dbg.target.TargetBreakpointLocation;
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet; import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.ListenerSet;
@TargetObjectSchemaInfo( @TargetObjectSchemaInfo(name = "BreakpointSpec", elements = { //
name = "BreakpointSpec", @TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class) }, attributes = {
elements = { //
@TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class)
},
attributes = {
@TargetAttributeType(name = "Type", type = String.class), @TargetAttributeType(name = "Type", type = String.class),
@TargetAttributeType(name = "Valid", type = Boolean.class), @TargetAttributeType(name = "Valid", type = Boolean.class),
@TargetAttributeType(name = "Enabled", type = Boolean.class), @TargetAttributeType(name = "Enabled", type = Boolean.class),
@TargetAttributeType(name = "Count", type = Long.class), @TargetAttributeType(name = "Count", type = Long.class),
@TargetAttributeType(type = Void.class) @TargetAttributeType(type = Void.class) }, canonicalContainer = true)
},
canonicalContainer = true)
public class LldbModelTargetBreakpointSpecImpl extends LldbModelTargetAbstractXpointSpec { public class LldbModelTargetBreakpointSpecImpl extends LldbModelTargetAbstractXpointSpec {
protected final ListenerSet<TargetBreakpointAction> actions = protected final ListenerSet<TargetBreakpointAction> actions =
new ListenerSet<>(TargetBreakpointAction.class) { new ListenerSet<>(TargetBreakpointAction.class, false);
// Use strong references on actions
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
return new LinkedHashMap<>();
};
};
public LldbModelTargetBreakpointSpecImpl(LldbModelTargetBreakpointContainer breakpoints, public LldbModelTargetBreakpointSpecImpl(LldbModelTargetBreakpointContainer breakpoints,
Object info) { Object info) {

View file

@ -34,7 +34,6 @@ import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.ListenerSet;
import utilities.util.ProxyUtilities; import utilities.util.ProxyUtilities;
@ -60,8 +59,8 @@ public class DelegateGadpClientTargetObject
for (Map.Entry<K, MethodHandle> ent : that.handles.entrySet()) { for (Map.Entry<K, MethodHandle> ent : that.handles.entrySet()) {
MethodHandle old = handles.put(ent.getKey(), ent.getValue()); MethodHandle old = handles.put(ent.getKey(), ent.getValue());
if (old != null) { if (old != null) {
throw new AssertionError("Conflict over handler for " + ent.getKey() + throw new AssertionError("Conflict over handler for " + ent.getKey() + ": " +
": " + old + " and " + ent.getValue()); old + " and " + ent.getValue());
} }
} }
} }
@ -161,8 +160,8 @@ public class DelegateGadpClientTargetObject
Set<Class<? extends TargetObject>> allMixins = new HashSet<>(mixins); Set<Class<? extends TargetObject>> allMixins = new HashSet<>(mixins);
allMixins.add(GadpClientTargetObject.class); allMixins.add(GadpClientTargetObject.class);
this.eventHandlers = EVENT_HANDLER_MAPS_BY_COMPOSITION.computeIfAbsent(allMixins, this.eventHandlers =
GadpEventHandlerMap::new); EVENT_HANDLER_MAPS_BY_COMPOSITION.computeIfAbsent(allMixins, GadpEventHandlerMap::new);
} }
@Override @Override
@ -187,11 +186,14 @@ public class DelegateGadpClientTargetObject
@Override @Override
public CompletableFuture<Void> resync(RefreshBehavior attributes, RefreshBehavior elements) { public CompletableFuture<Void> resync(RefreshBehavior attributes, RefreshBehavior elements) {
return client.sendChecked(Gadp.ResyncRequest.newBuilder() return client
.sendChecked(
Gadp.ResyncRequest.newBuilder()
.setPath(GadpValueUtils.makePath(path)) .setPath(GadpValueUtils.makePath(path))
.setAttributes(attributes.equals(RefreshBehavior.REFRESH_ALWAYS)) .setAttributes(attributes.equals(RefreshBehavior.REFRESH_ALWAYS))
.setElements(elements.equals(RefreshBehavior.REFRESH_ALWAYS)), .setElements(elements.equals(RefreshBehavior.REFRESH_ALWAYS)),
Gadp.ResyncReply.getDefaultInstance()).thenApply(rep -> null); Gadp.ResyncReply.getDefaultInstance())
.thenApply(rep -> null);
} }
@Override @Override
@ -238,9 +240,11 @@ public class DelegateGadpClientTargetObject
public synchronized CompletableFuture<Void> invalidateCaches() { public synchronized CompletableFuture<Void> invalidateCaches() {
assertValid(); assertValid();
doClearCaches(); doClearCaches();
return client.sendChecked(Gadp.CacheInvalidateRequest.newBuilder() return client
.setPath(GadpValueUtils.makePath(path)), .sendChecked(
Gadp.CacheInvalidateReply.getDefaultInstance()).thenApply(rep -> null); Gadp.CacheInvalidateRequest.newBuilder().setPath(GadpValueUtils.makePath(path)),
Gadp.CacheInvalidateReply.getDefaultInstance())
.thenApply(rep -> null);
} }
protected synchronized CachedMemory getMemoryCache(AddressSpace space) { protected synchronized CachedMemory getMemoryCache(AddressSpace space) {
@ -276,12 +280,7 @@ public class DelegateGadpClientTargetObject
protected synchronized ListenerSet<TargetBreakpointAction> getActions(boolean createIfAbsent) { protected synchronized ListenerSet<TargetBreakpointAction> getActions(boolean createIfAbsent) {
if (actions == null && createIfAbsent) { if (actions == null && createIfAbsent) {
actions = new ListenerSet<>(TargetBreakpointAction.class) { actions = new ListenerSet<>(TargetBreakpointAction.class, false);
// Want strong references on actions
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
return new LinkedHashMap<>();
};
};
} }
return actions; return actions;
} }

View file

@ -323,10 +323,10 @@ public class GadpClient extends AbstractDebuggerObjectModel
protected void channelStateChanged(ChannelState old, ChannelState set, protected void channelStateChanged(ChannelState old, ChannelState set,
DebuggerModelClosedReason reason) { DebuggerModelClosedReason reason) {
if (old == ChannelState.NEGOTIATING && set == ChannelState.ACTIVE) { if (old == ChannelState.NEGOTIATING && set == ChannelState.ACTIVE) {
listeners.fire.modelOpened(); listeners.invoke().modelOpened();
} }
else if (old == ChannelState.ACTIVE && set == ChannelState.CLOSED) { else if (old == ChannelState.ACTIVE && set == ChannelState.CLOSED) {
listeners.fire.modelClosed(reason); listeners.invoke().modelClosed(reason);
root.invalidateSubtree(root, "GADP Client disconnected"); root.invalidateSubtree(root, "GADP Client disconnected");
messageMatcher.flush(new DebuggerModelTerminatingException("GADP Client disconnected")); messageMatcher.flush(new DebuggerModelTerminatingException("GADP Client disconnected"));
} }

View file

@ -77,7 +77,7 @@ public interface GadpClientTargetBreakpointSpecContainer
GadpClientTargetBreakpointSpec specObj = (GadpClientTargetBreakpointSpec) spec; GadpClientTargetBreakpointSpec specObj = (GadpClientTargetBreakpointSpec) spec;
ListenerSet<TargetBreakpointAction> actions = specObj.getDelegate().getActions(false); ListenerSet<TargetBreakpointAction> actions = specObj.getDelegate().getActions(false);
if (actions != null) { if (actions != null) {
actions.fire.breakpointHit(specObj, trapped, frame, breakpoint); actions.invoke().breakpointHit(specObj, trapped, frame, breakpoint);
} }
} }
} }

View file

@ -41,7 +41,7 @@ public class JdiEventHandler implements Runnable {
protected final AsyncReference<Integer, JdiCause> state = protected final AsyncReference<Integer, JdiCause> state =
new AsyncReference<>(ThreadReference.THREAD_STATUS_NOT_STARTED); new AsyncReference<>(ThreadReference.THREAD_STATUS_NOT_STARTED);
public final ListenerSet<JdiEventsListener> listenersEvent = public final ListenerSet<JdiEventsListener> listenersEvent =
new ListenerSet<>(JdiEventsListener.class); new ListenerSet<>(JdiEventsListener.class, true);
protected final ExecutorService eventThread = Executors.newSingleThreadExecutor(); protected final ExecutorService eventThread = Executors.newSingleThreadExecutor();
public JdiEventHandler() { public JdiEventHandler() {
@ -116,7 +116,7 @@ public class JdiEventHandler implements Runnable {
else if (eventSet.suspendPolicy() == EventRequest.SUSPEND_ALL) { else if (eventSet.suspendPolicy() == EventRequest.SUSPEND_ALL) {
setCurrentThread(eventSet); setCurrentThread(eventSet);
event( event(
() -> listenersEvent.fire.processStop(eventSet, JdiCause.Causes.UNCLAIMED), () -> listenersEvent.invoke().processStop(eventSet, JdiCause.Causes.UNCLAIMED),
"processStopped"); "processStopped");
} }
} }
@ -214,7 +214,7 @@ public class JdiEventHandler implements Runnable {
/* /*
* Inform jdb command line processor that jdb is being shutdown. JDK-8154144. * 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"); "processStopped");
return null; ///false; return null; ///false;
} }
@ -301,7 +301,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processBreakpoint(BreakpointEvent evt) { protected DebugStatus processBreakpoint(BreakpointEvent evt) {
event(() -> listenersEvent.fire.breakpointHit(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().breakpointHit(evt, JdiCause.Causes.UNCLAIMED),
"breakpointHit"); "breakpointHit");
return DebugStatus.BREAK; return DebugStatus.BREAK;
} }
@ -314,7 +314,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processException(ExceptionEvent evt) { protected DebugStatus processException(ExceptionEvent evt) {
event(() -> listenersEvent.fire.exceptionHit(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().exceptionHit(evt, JdiCause.Causes.UNCLAIMED),
"exceptionHit"); "exceptionHit");
return DebugStatus.BREAK; return DebugStatus.BREAK;
} }
@ -327,7 +327,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processMethodEntry(MethodEntryEvent evt) { 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; return DebugStatus.GO;
} }
@ -339,7 +339,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processMethodExit(MethodExitEvent evt) { 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; return DebugStatus.GO;
} }
@ -351,7 +351,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processClassPrepare(ClassPrepareEvent evt) { protected DebugStatus processClassPrepare(ClassPrepareEvent evt) {
event(() -> listenersEvent.fire.classPrepare(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().classPrepare(evt, JdiCause.Causes.UNCLAIMED),
"classPrepare"); "classPrepare");
/* /*
if (!Env.specList.resolve(cle)) { if (!Env.specList.resolve(cle)) {
@ -372,7 +372,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processClassUnload(ClassUnloadEvent evt) { 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; return DebugStatus.GO;
} }
@ -384,7 +384,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processMCEntered(MonitorContendedEnteredEvent evt) { protected DebugStatus processMCEntered(MonitorContendedEnteredEvent evt) {
event(() -> listenersEvent.fire.monitorContendedEntered(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().monitorContendedEntered(evt, JdiCause.Causes.UNCLAIMED),
"monitorContendedEntered"); "monitorContendedEntered");
return DebugStatus.GO; return DebugStatus.GO;
} }
@ -397,7 +397,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processMCEnter(MonitorContendedEnterEvent evt) { protected DebugStatus processMCEnter(MonitorContendedEnterEvent evt) {
event(() -> listenersEvent.fire.monitorContendedEnter(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().monitorContendedEnter(evt, JdiCause.Causes.UNCLAIMED),
"monitorContendedEnter"); "monitorContendedEnter");
return DebugStatus.GO; return DebugStatus.GO;
} }
@ -410,7 +410,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processMonitorWaited(MonitorWaitedEvent evt) { protected DebugStatus processMonitorWaited(MonitorWaitedEvent evt) {
event(() -> listenersEvent.fire.monitorWaited(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().monitorWaited(evt, JdiCause.Causes.UNCLAIMED),
"monitorWaited"); "monitorWaited");
return DebugStatus.GO; return DebugStatus.GO;
} }
@ -423,7 +423,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processMonitorWait(MonitorWaitEvent evt) { 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; return DebugStatus.GO;
} }
@ -436,7 +436,7 @@ public class JdiEventHandler implements Runnable {
*/ */
protected DebugStatus processStep(StepEvent evt) { protected DebugStatus processStep(StepEvent evt) {
evt.request().disable(); 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; return DebugStatus.STEP_INTO;
} }
@ -448,7 +448,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processWatchpoint(WatchpointEvent evt) { protected DebugStatus processWatchpoint(WatchpointEvent evt) {
event(() -> listenersEvent.fire.watchpointHit(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().watchpointHit(evt, JdiCause.Causes.UNCLAIMED),
"watchpointHit"); "watchpointHit");
return DebugStatus.BREAK; return DebugStatus.BREAK;
} }
@ -461,7 +461,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processAccessWatchpoint(AccessWatchpointEvent evt) { protected DebugStatus processAccessWatchpoint(AccessWatchpointEvent evt) {
event(() -> listenersEvent.fire.accessWatchpointHit(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().accessWatchpointHit(evt, JdiCause.Causes.UNCLAIMED),
"accessWatchpointHit"); "accessWatchpointHit");
return DebugStatus.BREAK; return DebugStatus.BREAK;
} }
@ -474,7 +474,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processWatchpointModification(ModificationWatchpointEvent evt) { protected DebugStatus processWatchpointModification(ModificationWatchpointEvent evt) {
event(() -> listenersEvent.fire.watchpointModified(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().watchpointModified(evt, JdiCause.Causes.UNCLAIMED),
"watchpointModified"); "watchpointModified");
return DebugStatus.GO; return DebugStatus.GO;
} }
@ -487,7 +487,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processThreadDeath(ThreadDeathEvent evt) { protected DebugStatus processThreadDeath(ThreadDeathEvent evt) {
event(() -> listenersEvent.fire.threadExited(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().threadExited(evt, JdiCause.Causes.UNCLAIMED),
"threadExited"); "threadExited");
JdiThreadInfo.removeThread(evt.thread()); JdiThreadInfo.removeThread(evt.thread());
return DebugStatus.GO; return DebugStatus.GO;
@ -502,7 +502,7 @@ public class JdiEventHandler implements Runnable {
*/ */
protected DebugStatus processThreadStart(ThreadStartEvent evt) { protected DebugStatus processThreadStart(ThreadStartEvent evt) {
JdiThreadInfo.addThread(evt.thread()); JdiThreadInfo.addThread(evt.thread());
event(() -> listenersEvent.fire.threadStarted(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().threadStarted(evt, JdiCause.Causes.UNCLAIMED),
"threadStarted"); "threadStarted");
return DebugStatus.GO; return DebugStatus.GO;
} }
@ -516,7 +516,7 @@ public class JdiEventHandler implements Runnable {
*/ */
protected DebugStatus processVMDeath(VMDeathEvent evt) { protected DebugStatus processVMDeath(VMDeathEvent evt) {
shutdownMessageKey = "The application exited"; 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; return DebugStatus.BREAK;
} }
@ -529,7 +529,7 @@ public class JdiEventHandler implements Runnable {
*/ */
protected DebugStatus processVMDisconnect(VMDisconnectEvent evt) { protected DebugStatus processVMDisconnect(VMDisconnectEvent evt) {
shutdownMessageKey = "The application has been disconnected"; shutdownMessageKey = "The application has been disconnected";
event(() -> listenersEvent.fire.vmDisconnected(evt, JdiCause.Causes.UNCLAIMED), event(() -> listenersEvent.invoke().vmDisconnected(evt, JdiCause.Causes.UNCLAIMED),
"vmDisconnected"); "vmDisconnected");
return DebugStatus.BREAK; return DebugStatus.BREAK;
} }
@ -542,7 +542,7 @@ public class JdiEventHandler implements Runnable {
* @return * @return
*/ */
protected DebugStatus processVMStart(VMStartEvent evt) { 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; return DebugStatus.BREAK;
} }

View file

@ -41,9 +41,9 @@ public class JdiManagerImpl implements JdiManager {
private final Map<String, VirtualMachine> unmodifiableVMs = Collections.unmodifiableMap(vms); private final Map<String, VirtualMachine> unmodifiableVMs = Collections.unmodifiableMap(vms);
protected final ListenerSet<JdiTargetOutputListener> listenersTargetOutput = protected final ListenerSet<JdiTargetOutputListener> listenersTargetOutput =
new ListenerSet<>(JdiTargetOutputListener.class); new ListenerSet<>(JdiTargetOutputListener.class, true);
protected final ListenerSet<JdiConsoleOutputListener> listenersConsoleOutput = protected final ListenerSet<JdiConsoleOutputListener> listenersConsoleOutput =
new ListenerSet<>(JdiConsoleOutputListener.class); new ListenerSet<>(JdiConsoleOutputListener.class, true);
protected final ExecutorService eventThread = Executors.newSingleThreadExecutor(); protected final ExecutorService eventThread = Executors.newSingleThreadExecutor();
protected JdiEventHandler globalEventHandler = new JdiEventHandler(); protected JdiEventHandler globalEventHandler = new JdiEventHandler();

View file

@ -15,7 +15,8 @@
*/ */
package ghidra.dbg.jdi.model; package ghidra.dbg.jdi.model;
import java.util.*; import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
@ -26,21 +27,12 @@ import ghidra.dbg.target.TargetBreakpointSpec;
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet; import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
import ghidra.dbg.target.schema.TargetAttributeType; import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.target.schema.TargetObjectSchemaInfo; import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.ListenerSet;
@TargetObjectSchemaInfo( @TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = {
name = "BreakpointSpec", @TargetAttributeType(name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, type = JdiModelTargetBreakpointContainer.class),
attributes = { @TargetAttributeType(name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, type = JdiModelTargetBreakpointSpec.class),
@TargetAttributeType( @TargetAttributeType(type = Void.class) }, canonicalContainer = true)
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 public class JdiModelTargetBreakpointSpec extends JdiModelTargetObjectImpl
implements TargetBreakpointSpec, JdiModelTargetDeletable { implements TargetBreakpointSpec, JdiModelTargetDeletable {
@ -48,12 +40,7 @@ public class JdiModelTargetBreakpointSpec extends JdiModelTargetObjectImpl
protected TargetBreakpointKindSet kinds; protected TargetBreakpointKindSet kinds;
protected final ListenerSet<TargetBreakpointAction> actions = protected final ListenerSet<TargetBreakpointAction> actions =
new ListenerSet<>(TargetBreakpointAction.class) { new ListenerSet<>(TargetBreakpointAction.class, false);
// Use strong references on actions
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
return new LinkedHashMap<>();
}
};
public JdiModelTargetBreakpointSpec(JdiModelTargetBreakpointContainer breakpoints, public JdiModelTargetBreakpointSpec(JdiModelTargetBreakpointContainer breakpoints,
JdiBreakpointInfo info, boolean isElement) { JdiBreakpointInfo info, boolean isElement) {

View file

@ -274,7 +274,7 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen
} }
targetVM.vmStateChanged(targetState, reason); targetVM.vmStateChanged(targetState, reason);
JdiEventHandler eventHandler = getManager().getEventHandler(targetVM.vm); JdiEventHandler eventHandler = getManager().getEventHandler(targetVM.vm);
eventHandler.listenersEvent.fire.threadStateChanged(thread, state, eventHandler.listenersEvent.invoke().threadStateChanged(thread, state,
JdiCause.Causes.UNCLAIMED, reason); JdiCause.Causes.UNCLAIMED, reason);
} }

View file

@ -217,7 +217,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
trackingLabel.setText(""); trackingLabel.setText("");
trackingLabel.setToolTipText(""); trackingLabel.setToolTipText("");
trackingLabel.setForeground(Colors.FOREGROUND); trackingLabel.setForeground(Colors.FOREGROUND);
trackingSpecChangeListeners.fire.locationTrackingSpecChanged(spec); trackingSpecChangeListeners.invoke().locationTrackingSpecChanged(spec);
} }
@Override @Override
@ -291,7 +291,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
protected final ForListingReadsMemoryTrait readsMemTrait; protected final ForListingReadsMemoryTrait readsMemTrait;
protected final ListenerSet<LocationTrackingSpecChangeListener> trackingSpecChangeListeners = protected final ListenerSet<LocationTrackingSpecChangeListener> trackingSpecChangeListeners =
new ListenerSet<>(LocationTrackingSpecChangeListener.class); new ListenerSet<>(LocationTrackingSpecChangeListener.class, true);
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel(); protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
protected final JLabel trackingLabel = new JLabel(); protected final JLabel trackingLabel = new JLabel();

View file

@ -51,7 +51,7 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
protected boolean showHidden = false; protected boolean showHidden = false;
private final ListenerSet<CellActivationListener> cellActivationListeners = private final ListenerSet<CellActivationListener> cellActivationListeners =
new ListenerSet<>(CellActivationListener.class); new ListenerSet<>(CellActivationListener.class, true);
public AbstractQueryTablePanel(Plugin plugin) { public AbstractQueryTablePanel(Plugin plugin) {
super(new BorderLayout()); super(new BorderLayout());
@ -236,6 +236,6 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
} }
protected void fireCellActivated() { protected void fireCellActivated() {
cellActivationListeners.fire.cellActivated(table); cellActivationListeners.invoke().cellActivated(table);
} }
} }

View file

@ -815,7 +815,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
private final Object lock = new Object(); private final Object lock = new Object();
private final ListenerSet<LogicalBreakpointsChangeListener> changeListeners = private final ListenerSet<LogicalBreakpointsChangeListener> changeListeners =
new ListenerSet<>(LogicalBreakpointsChangeListener.class); new ListenerSet<>(LogicalBreakpointsChangeListener.class, true);
private final TrackRecordersListener targetsListener = new TrackRecordersListener(); private final TrackRecordersListener targetsListener = new TrackRecordersListener();
private final TrackMappingsListener mappingListener = new TrackMappingsListener(); private final TrackMappingsListener mappingListener = new TrackMappingsListener();
@ -835,7 +835,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
protected void processChange(Consumer<ChangeCollector> processor, String description) { protected void processChange(Consumer<ChangeCollector> processor, String description) {
executor.submit(() -> { executor.submit(() -> {
// Invoke change callbacks without the lock! (try must surround sync) // 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) { synchronized (lock) {
processor.accept(c); processor.accept(c);
} }

View file

@ -213,7 +213,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
private final Map<Trace, ControlMode> currentModes = new HashMap<>(); private final Map<Trace, ControlMode> currentModes = new HashMap<>();
private final ListenerSet<ControlModeChangeListener> listeners = private final ListenerSet<ControlModeChangeListener> listeners =
new ListenerSet<>(ControlModeChangeListener.class); new ListenerSet<>(ControlModeChangeListener.class, true);
@Override @Override
public ControlMode getCurrentMode(Trace trace) { public ControlMode getCurrentMode(Trace trace) {
@ -232,7 +232,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
} }
} }
if (newMode != oldMode) { if (newMode != oldMode) {
listeners.fire.modeChanged(trace, newMode); listeners.invoke().modeChanged(trace, newMode);
tool.contextChanged(null); tool.contextChanged(null);
} }
} }
@ -280,7 +280,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
} }
} }
if (newMode != oldMode) { if (newMode != oldMode) {
listeners.fire.modeChanged(trace, newMode); listeners.invoke().modeChanged(trace, newMode);
tool.contextChanged(null); tool.contextChanged(null);
} }
} }

View file

@ -295,7 +295,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
.forgetValues((key, l) -> true); .forgetValues((key, l) -> true);
protected final Map<CachedEmulator, Integer> busy = new LinkedHashMap<>(); protected final Map<CachedEmulator, Integer> busy = new LinkedHashMap<>();
protected final ListenerSet<EmulatorStateListener> stateListeners = protected final ListenerSet<EmulatorStateListener> stateListeners =
new ListenerSet<>(EmulatorStateListener.class); new ListenerSet<>(EmulatorStateListener.class, true);
class BusyEmu implements AutoCloseable { class BusyEmu implements AutoCloseable {
private final CachedEmulator ce; private final CachedEmulator ce;
@ -314,7 +314,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
} }
} }
if (fire) { if (fire) {
stateListeners.fire.running(ce); stateListeners.invoke().running(ce);
} }
} }
@ -332,7 +332,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
} }
} }
if (fire) { if (fire) {
stateListeners.fire.stopped(ce); stateListeners.invoke().stopped(ce);
} }
} }

View file

@ -95,7 +95,7 @@ public class DebuggerModelServicePlugin extends Plugin
models.remove(model); models.remove(model);
} }
model.removeModelListener(this); model.removeModelListener(this);
modelListeners.fire.elementRemoved(model); modelListeners.invoke().elementRemoved(model);
if (currentModel == model) { if (currentModel == model) {
activateModel(null); activateModel(null);
} }
@ -155,11 +155,11 @@ public class DebuggerModelServicePlugin extends Plugin
protected final Map<TargetObject, TraceRecorder> recordersByTarget = new WeakHashMap<>(); protected final Map<TargetObject, TraceRecorder> recordersByTarget = new WeakHashMap<>();
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners = 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 = 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 = 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 ChangeListener classChangeListener = new ChangeListenerForFactoryInstances();
protected final ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders(); protected final ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders();
@ -264,7 +264,7 @@ public class DebuggerModelServicePlugin extends Plugin
"Invalidated before or during add to service"); "Invalidated before or during add to service");
} }
} }
modelListeners.fire.elementAdded(model); modelListeners.invoke().elementAdded(model);
return true; return true;
} }
@ -276,7 +276,7 @@ public class DebuggerModelServicePlugin extends Plugin
return false; return false;
} }
} }
modelListeners.fire.elementRemoved(model); modelListeners.invoke().elementRemoved(model);
return true; return true;
} }
@ -315,7 +315,7 @@ public class DebuggerModelServicePlugin extends Plugin
}); });
recordersByTarget.put(target, recorder); 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 // NOTE: It's possible the recorder stopped recording before we installed the listener
if (!recorder.isRecording()) { if (!recorder.isRecording()) {
doRemoveRecorder(recorder); doRemoveRecorder(recorder);
@ -385,7 +385,7 @@ public class DebuggerModelServicePlugin extends Plugin
} }
old.removeListener(listenerOnRecorders); old.removeListener(listenerOnRecorders);
} }
recorderListeners.fire.elementRemoved(recorder); recorderListeners.invoke().elementRemoved(recorder);
} }
@Override @Override
@ -423,7 +423,7 @@ public class DebuggerModelServicePlugin extends Plugin
diff.removeAll(newFactories); diff.removeAll(newFactories);
for (DebuggerModelFactory factory : diff) { for (DebuggerModelFactory factory : diff) {
factories.remove(factory); factories.remove(factory);
factoryListeners.fire.elementRemoved(factory); factoryListeners.invoke().elementRemoved(factory);
} }
diff.clear(); diff.clear();
@ -431,7 +431,7 @@ public class DebuggerModelServicePlugin extends Plugin
diff.removeAll(factories); diff.removeAll(factories);
for (DebuggerModelFactory factory : diff) { for (DebuggerModelFactory factory : diff) {
factories.add(factory); 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; removed = recordersByTarget.remove(recorder.getTarget()) != null;
} }
if (removed) { if (removed) {
recorderListeners.fire.elementRemoved(recorder); recorderListeners.invoke().elementRemoved(recorder);
} }
} }

View file

@ -72,23 +72,10 @@ import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@PluginInfo( @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 = {
shortDescription = "Debugger models manager service (proxy to front-end)", ProgramActivatedPluginEvent.class, ProgramClosedPluginEvent.class, }, servicesRequired = {
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, DebuggerTargetService.class,
DebuggerTraceManagerService.class, DebuggerTraceManagerService.class, }, servicesProvided = { DebuggerModelService.class, })
},
servicesProvided = {
DebuggerModelService.class,
})
public class DebuggerModelServiceProxyPlugin extends Plugin public class DebuggerModelServiceProxyPlugin extends Plugin
implements DebuggerModelServiceInternal { implements DebuggerModelServiceInternal {
@ -158,17 +145,17 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
implements CollectionChangeListener<DebuggerModelFactory> { implements CollectionChangeListener<DebuggerModelFactory> {
@Override @Override
public void elementAdded(DebuggerModelFactory element) { public void elementAdded(DebuggerModelFactory element) {
factoryListeners.fire.elementAdded(element); factoryListeners.invoke().elementAdded(element);
} }
@Override @Override
public void elementRemoved(DebuggerModelFactory element) { public void elementRemoved(DebuggerModelFactory element) {
factoryListeners.fire.elementRemoved(element); factoryListeners.invoke().elementRemoved(element);
} }
@Override @Override
public void elementModified(DebuggerModelFactory element) { 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> { implements CollectionChangeListener<DebuggerObjectModel> {
@Override @Override
public void elementAdded(DebuggerObjectModel element) { public void elementAdded(DebuggerObjectModel element) {
modelListeners.fire.elementAdded(element); modelListeners.invoke().elementAdded(element);
if (currentModel == null) { if (currentModel == null) {
activateModel(element); activateModel(element);
} }
@ -187,12 +174,12 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
if (currentModel == element) { if (currentModel == element) {
activateModel(null); activateModel(null);
} }
modelListeners.fire.elementRemoved(element); modelListeners.invoke().elementRemoved(element);
} }
@Override @Override
public void elementModified(DebuggerObjectModel element) { 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> { implements CollectionChangeListener<TraceRecorder> {
@Override @Override
public void elementAdded(TraceRecorder element) { public void elementAdded(TraceRecorder element) {
recorderListeners.fire.elementAdded(element); recorderListeners.invoke().elementAdded(element);
Swing.runIfSwingOrRunLater(() -> { Swing.runIfSwingOrRunLater(() -> {
TraceRecorderTarget target = new TraceRecorderTarget(tool, element); TraceRecorderTarget target = new TraceRecorderTarget(tool, element);
targets.put(element, target); targets.put(element, target);
@ -210,15 +197,16 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
@Override @Override
public void elementRemoved(TraceRecorder element) { public void elementRemoved(TraceRecorder element) {
recorderListeners.fire.elementRemoved(element); recorderListeners.invoke().elementRemoved(element);
Swing.runIfSwingOrRunLater(() -> { Swing.runIfSwingOrRunLater(() -> {
targetService.withdrawTarget(Objects.requireNonNull(targets.get(element))); targetService.withdrawTarget(Objects.requireNonNull(targets.get(element)));
}); });
} }
@Override @Override
public void elementModified(TraceRecorder element) { public void elementModified(TraceRecorder element) {
recorderListeners.fire.elementModified(element); recorderListeners.invoke().elementModified(element);
} }
} }
@ -249,11 +237,11 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
DockingAction actionDisconnectAll; DockingAction actionDisconnectAll;
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners = 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 = 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 = 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<>(); 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 // Note, I have to give an enabledWhen, otherwise any context change re-enables it
MultiStateActionBuilder<DebuggerProgramLaunchOffer> builderDebugProgram = MultiStateActionBuilder<DebuggerProgramLaunchOffer> builderDebugProgram =
DebugProgramAction.buttonBuilder(this, delegate); DebugProgramAction.buttonBuilder(this, delegate);
actionDebugProgram = builderDebugProgram actionDebugProgram = builderDebugProgram.enabledWhen(ctx -> currentProgram != null)
.enabledWhen(ctx -> currentProgram != null)
.onAction(this::debugProgramButtonActivated) .onAction(this::debugProgramButtonActivated)
.onActionStateChanged(this::debugProgramStateActivated) .onActionStateChanged(this::debugProgramStateActivated)
.addState(DUMMY_LAUNCH_STATE) .addState(DUMMY_LAUNCH_STATE)
@ -333,8 +320,8 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
protected void writeMostRecentLaunches(Program program, List<String> mrl) { protected void writeMostRecentLaunches(Program program, List<String> mrl) {
ProgramUserData userData = program.getProgramUserData(); ProgramUserData userData = program.getProgramUserData();
try (Transaction tid = userData.openTransaction()) { try (Transaction tid = userData.openTransaction()) {
StringPropertyMap prop = userData StringPropertyMap prop =
.getStringProperty(getName(), KEY_MOST_RECENT_LAUNCHES, true); userData.getStringProperty(getName(), KEY_MOST_RECENT_LAUNCHES, true);
Address min = program.getAddressFactory().getDefaultAddressSpace().getMinAddress(); Address min = program.getAddressFactory().getDefaultAddressSpace().getMinAddress();
prop.add(min, mrl.stream().collect(Collectors.joining(";"))); prop.add(min, mrl.stream().collect(Collectors.joining(";")));
} }
@ -425,8 +412,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
List<DebuggerProgramLaunchOffer> offers = program == null ? List.of() List<DebuggerProgramLaunchOffer> offers = program == null ? List.of()
: getProgramLaunchOffers(program).collect(Collectors.toList()); : getProgramLaunchOffers(program).collect(Collectors.toList());
List<ActionState<DebuggerProgramLaunchOffer>> states = offers.stream() List<ActionState<DebuggerProgramLaunchOffer>> states = offers.stream()
.map(o -> new ActionState<>(o.getButtonTitle(), .map(o -> new ActionState<>(o.getButtonTitle(), o.getIcon(), o))
o.getIcon(), o))
.collect(Collectors.toList()); .collect(Collectors.toList());
if (!states.isEmpty()) { if (!states.isEmpty()) {
actionDebugProgram.setActionStates(states); actionDebugProgram.setActionStates(states);

View file

@ -46,7 +46,7 @@ public class DefaultProcessRecorder implements ManagedProcessRecorder {
protected void processMemoryAccessibilityChanged(boolean old, protected void processMemoryAccessibilityChanged(boolean old,
boolean acc, Void __) { boolean acc, Void __) {
recorder.getListeners().fire.processMemoryAccessibilityChanged(recorder); recorder.getListeners().invoke().processMemoryAccessibilityChanged(recorder);
} }
public CompletableFuture<byte[]> readProcessMemory(Address start, int length) { public CompletableFuture<byte[]> readProcessMemory(Address start, int length) {

View file

@ -154,7 +154,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
return AsyncUtils.nil(); return AsyncUtils.nil();
} }
return initRegMapper(descs).thenAccept(__ -> { return initRegMapper(descs).thenAccept(__ -> {
recorder.getListeners().fire.registerBankMapped(recorder); recorder.getListeners().invoke().registerBankMapped(recorder);
}).exceptionally(ex -> { }).exceptionally(ex -> {
Msg.error(this, "Could not intialize register mapper", ex); Msg.error(this, "Could not intialize register mapper", ex);
return null; return null;

View file

@ -52,7 +52,7 @@ public class DefaultTimeRecorder {
RecorderPermanentTransaction tid) { RecorderPermanentTransaction tid) {
if (tid != null) { if (tid != null) {
doAdvanceSnap(description, eventThread); doAdvanceSnap(description, eventThread);
recorder.getListeners().fire.snapAdvanced(recorder, getSnap()); recorder.getListeners().invoke().snapAdvanced(recorder, getSnap());
return; return;
} }
// NB. The also serves as the snap counter, so it must be on the service thread // 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)) { RecorderPermanentTransaction.start(trace, description)) {
doAdvanceSnap(description, eventThread); doAdvanceSnap(description, eventThread);
} }
recorder.getListeners().fire.snapAdvanced(recorder, getSnap()); recorder.getListeners().invoke().snapAdvanced(recorder, getSnap());
} }
} }

View file

@ -367,7 +367,7 @@ public class DefaultTraceRecorder implements TraceRecorder {
@Override @Override
public void stopRecording() { public void stopRecording() {
invalidate(); invalidate();
getListeners().fire.recordingStopped(this); getListeners().invoke().recordingStopped(this);
} }
protected void invalidate() { protected void invalidate() {

View file

@ -58,7 +58,7 @@ public class TraceObjectManager {
//private AbstractRecorderRegisterSet threadRegisters; //private AbstractRecorderRegisterSet threadRegisters;
private final ListenerSet<TraceRecorderListener> listeners = private final ListenerSet<TraceRecorderListener> listeners =
new ListenerSet<>(TraceRecorderListener.class); new ListenerSet<>(TraceRecorderListener.class, true);
protected final Set<TargetBreakpointLocation> breakpoints = new HashSet<>(); protected final Set<TargetBreakpointLocation> breakpoints = new HashSet<>();

View file

@ -81,7 +81,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
protected final ListenerForRecord listenerForRecord; protected final ListenerForRecord listenerForRecord;
protected final ListenerSet<TraceRecorderListener> listeners = 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. // TODO: I don't like this here. Should ask the model, not the recorder.
protected TargetObject curFocus; protected TargetObject curFocus;
@ -805,11 +805,11 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
} }
protected void fireSnapAdvanced(long key) { protected void fireSnapAdvanced(long key) {
listeners.fire.snapAdvanced(this, key); listeners.invoke().snapAdvanced(this, key);
} }
protected void fireRecordingStopped() { protected void fireRecordingStopped() {
listeners.fire.recordingStopped(this); listeners.invoke().recordingStopped(this);
} }
// TODO: Deprecate/remove the other callbacks: registerBankMapped, *accessibilityChanged // TODO: Deprecate/remove the other callbacks: registerBankMapped, *accessibilityChanged

View file

@ -544,7 +544,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
private final AsyncDebouncer<Void> changeDebouncer = private final AsyncDebouncer<Void> changeDebouncer =
new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100); new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100);
private final ListenerSet<DebuggerStaticMappingChangeListener> changeListeners = private final ListenerSet<DebuggerStaticMappingChangeListener> changeListeners =
new ListenerSet<>(DebuggerStaticMappingChangeListener.class); new ListenerSet<>(DebuggerStaticMappingChangeListener.class, true);
private Set<Trace> affectedTraces = new HashSet<>(); private Set<Trace> affectedTraces = new HashSet<>();
private Set<Program> affectedPrograms = new HashSet<>(); private Set<Program> affectedPrograms = new HashSet<>();
@ -576,7 +576,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
affectedTraces = new HashSet<>(); affectedTraces = new HashSet<>();
affectedPrograms = new HashSet<>(); affectedPrograms = new HashSet<>();
} }
changeListeners.fire.mappingsChanged(traces, programs); changeListeners.invoke().mappingsChanged(traces, programs);
} }
private void traceAffected(Trace trace) { private void traceAffected(Trace trace) {

View file

@ -27,15 +27,8 @@ import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.ListenerSet;
@PluginInfo( @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 = {
shortDescription = "Debugger targets manager service", DebuggerTargetService.class, })
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 class DebuggerTargetServicePlugin extends Plugin implements DebuggerTargetService {
public DebuggerTargetServicePlugin(PluginTool tool) { 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 Map<Trace, Target> targets = new HashMap<>();
private final ListenerSet<TargetPublicationListener> listeners = private final ListenerSet<TargetPublicationListener> listeners =
new ListenerSet<>(TargetPublicationListener.class); new ListenerSet<>(TargetPublicationListener.class, true);
@Override @Override
public void publishTarget(Target target) { public void publishTarget(Target target) {
@ -53,7 +46,7 @@ public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTarge
notify = targets.put(target.getTrace(), target) != target; notify = targets.put(target.getTrace(), target) != target;
} }
if (notify) { 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; notify = targets.remove(target.getTrace()) == target;
} }
if (notify) { if (notify) {
listeners.fire.targetWithdrawn(target); listeners.invoke().targetWithdrawn(target);
} }
} }

View file

@ -34,8 +34,6 @@ import javax.swing.tree.TreePath;
import org.junit.*; import org.junit.*;
import org.junit.rules.TestName; import org.junit.rules.TestName;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import db.Transaction; import db.Transaction;
import docking.ActionContext; import docking.ActionContext;
@ -81,7 +79,7 @@ import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace; import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.datastruct.ListenerMap; import ghidra.util.datastruct.TestDataStructureErrorHandlerInstaller;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.ConsoleTaskMonitor; import ghidra.util.task.ConsoleTaskMonitor;
@ -103,8 +101,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
} }
@Override @Override
protected DebuggerRegisterMapper createRegisterMapper( protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
TargetRegisterContainer registers) {
return new DefaultDebuggerRegisterMapper(cSpec, registers, true); return new DefaultDebuggerRegisterMapper(cSpec, registers, true);
} }
} }
@ -481,9 +478,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
protected static void performEnabledAction(ActionContextProvider provider, protected static void performEnabledAction(ActionContextProvider provider,
DockingActionIf action, boolean wait) { DockingActionIf action, boolean wait) {
ActionContext context = waitForValue(() -> { ActionContext context = waitForValue(() -> {
ActionContext ctx = provider == null ActionContext ctx =
? new DefaultActionContext() provider == null ? new DefaultActionContext() : provider.getActionContext(null);
: provider.getActionContext(null);
if (!action.isEnabledForContext(ctx)) { if (!action.isEnabledForContext(ctx)) {
return null; return null;
} }
@ -556,15 +552,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
@Rule @Rule
public TestName name = new TestName(); 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 final ConsoleTaskMonitor monitor = new ConsoleTaskMonitor();
protected void waitRecorder(TraceRecorder recorder) throws Throwable { protected void waitRecorder(TraceRecorder recorder) throws Throwable {
@ -586,9 +574,16 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
waitForDomainObject(recorder.getTrace()); 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 @Before
public void setUp() throws Exception { public void setUp() throws Exception {
ListenerMap.clearErr();
env = new TestEnv(); env = new TestEnv();
tool = env.getTool(); tool = env.getTool();
@ -670,8 +665,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
populateTestModel(); populateTestModel();
TargetObject target = chooseTarget(); TargetObject target = chooseTarget();
TraceRecorder recorder = modelService.recordTarget(target, TraceRecorder recorder = modelService.recordTarget(target, createTargetTraceMapper(target),
createTargetTraceMapper(target), ActionSource.AUTOMATIC); ActionSource.AUTOMATIC);
waitRecorder(recorder); waitRecorder(recorder);
return recorder; return recorder;
@ -682,9 +677,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
protected void intoProject(DomainObject obj) { protected void intoProject(DomainObject obj) {
waitForDomainObject(obj); waitForDomainObject(obj);
DomainFolder rootFolder = tool.getProject() DomainFolder rootFolder = tool.getProject().getProjectData().getRootFolder();
.getProjectData()
.getRootFolder();
waitForCondition(() -> { waitForCondition(() -> {
try { try {
rootFolder.createFile(obj.getName(), obj, monitor); rootFolder.createFile(obj.getName(), obj, monitor);
@ -799,8 +792,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged); listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged);
} }
void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range, void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range, byte[] oldValue,
byte[] oldValue, byte[] newValue) { byte[] newValue) {
if (space.getThread() != traceThread) { if (space.getThread() != traceThread) {
return; return;
} }

View file

@ -36,8 +36,8 @@ import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.debug.api.action.ActionSource; import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.breakpoint.LogicalBreakpoint; import ghidra.debug.api.breakpoint.LogicalBreakpoint;
import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener;
import ghidra.debug.api.breakpoint.LogicalBreakpoint.State; import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener;
import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.control.ControlMode;
import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener; import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener;
@ -53,7 +53,6 @@ import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceStaticMapping; import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.ListenerMap;
public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDebuggerGUITest { public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDebuggerGUITest {
protected static final long TIMEOUT_MILLIS = protected static final long TIMEOUT_MILLIS =
@ -67,8 +66,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
* double-removes. 3) No extraneous updates. At the end of each test, the current set of * 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. * breakpoints in this listener should be verified against those reported by the service.
*/ */
protected class NoDuplicatesTrackingChangeListener protected class NoDuplicatesTrackingChangeListener implements LogicalBreakpointsChangeListener {
implements LogicalBreakpointsChangeListener {
private Set<LogicalBreakpoint> current = new HashSet<>(); private Set<LogicalBreakpoint> current = new HashSet<>();
@Override @Override
@ -152,8 +150,6 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
@Before @Before
public void setUpBreakpointServiceTest() throws Throwable { public void setUpBreakpointServiceTest() throws Throwable {
ListenerMap.clearErr();
addPlugin(tool, DebuggerLogicalBreakpointServicePlugin.class); addPlugin(tool, DebuggerLogicalBreakpointServicePlugin.class);
breakpointService = tool.getService(DebuggerLogicalBreakpointService.class); breakpointService = tool.getService(DebuggerLogicalBreakpointService.class);
mappingService = tool.getService(DebuggerStaticMappingService.class); mappingService = tool.getService(DebuggerStaticMappingService.class);
@ -197,7 +193,6 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
waitForLock(recorder3.getTrace()); waitForLock(recorder3.getTrace());
recorder3.stopRecording(); recorder3.stopRecording();
} }
ListenerMap.checkErr();
} }
catch (Throwable t) { catch (Throwable t) {
Msg.error(this, "Failed during tear down: " + 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<>(); Map<Program, Set<LogicalBreakpoint>> breaksByProgramViaPer = new HashMap<>();
for (Program prog : programManager.getAllOpenPrograms()) { for (Program prog : programManager.getAllOpenPrograms()) {
Set<LogicalBreakpoint> breaks = new HashSet<>(); Set<LogicalBreakpoint> breaks = new HashSet<>();
for (Entry<Address, Set<LogicalBreakpoint>> ent : breakpointService for (Entry<Address, Set<LogicalBreakpoint>> ent : breakpointService.getBreakpoints(prog)
.getBreakpoints(prog)
.entrySet()) { .entrySet()) {
for (LogicalBreakpoint lb : ent.getValue()) { for (LogicalBreakpoint lb : ent.getValue()) {
ProgramLocation loc = lb.getProgramLocation(); ProgramLocation loc = lb.getProgramLocation();
@ -264,8 +258,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
protected void addProgramTextBlock(Program p) throws Throwable { protected void addProgramTextBlock(Program p) throws Throwable {
try (Transaction tx = program.openTransaction("Add .text block")) { try (Transaction tx = program.openTransaction("Add .text block")) {
p.getMemory() p.getMemory()
.createInitializedBlock(".text", addr(p, 0x00400000), 0x1000, (byte) 0, .createInitializedBlock(".text", addr(p, 0x00400000), 0x1000, (byte) 0, monitor,
monitor, false); false);
} }
} }
@ -290,8 +284,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
DebuggerStaticMappingUtils.addMapping( DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(t, null, textRegion.getLifespan(), new DefaultTraceLocation(t, null, textRegion.getLifespan(),
textRegion.getMinAddress()), textRegion.getMinAddress()),
new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false);
false);
} }
} }
@ -329,8 +322,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
TargetBreakpointSpecContainer cont = getBreakpointContainer(r); TargetBreakpointSpecContainer cont = getBreakpointContainer(r);
cont.fetchElements().thenAccept(elements -> { cont.fetchElements().thenAccept(elements -> {
for (TargetObject obj : elements.values()) { for (TargetObject obj : elements.values()) {
if (!(obj instanceof TargetBreakpointSpec) || if (!(obj instanceof TargetBreakpointSpec) || !(obj instanceof TargetDeletable)) {
!(obj instanceof TargetDeletable)) {
continue; continue;
} }
TargetBreakpointSpec spec = (TargetBreakpointSpec) obj; TargetBreakpointSpec spec = (TargetBreakpointSpec) obj;
@ -394,8 +386,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
int total) { int total) {
assertEquals(total, breakpointService.getAllBreakpoints().size()); assertEquals(total, breakpointService.getAllBreakpoints().size());
LogicalBreakpoint enLb = Unique LogicalBreakpoint enLb =
.assertOne(breakpointService.getBreakpointsAt(trace, addr(trace, offset))); Unique.assertOne(breakpointService.getBreakpointsAt(trace, addr(trace, offset)));
assertNull(enLb.getProgramLocation()); assertNull(enLb.getProgramLocation());
assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), enLb.getKinds()); assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), enLb.getKinds());
@ -958,7 +950,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
addTextMapping(recorder3, text3, program); addTextMapping(recorder3, text3, program);
waitForSwing(); waitForSwing();
waitForPass(() -> { waitForPass(() -> {
assertEquals(2, mappingService assertEquals(2,
mappingService
.getOpenMappedLocations( .getOpenMappedLocations(
new ProgramLocation(program, addr(program, 0x00400123))) new ProgramLocation(program, addr(program, 0x00400123)))
.size()); .size());
@ -997,7 +990,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
addTextMapping(recorder3, text3, program); addTextMapping(recorder3, text3, program);
waitForSwing(); waitForSwing();
waitForPass(() -> { waitForPass(() -> {
assertEquals(2, mappingService assertEquals(2,
mappingService
.getOpenMappedLocations( .getOpenMappedLocations(
new ProgramLocation(program, addr(program, 0x00400123))) new ProgramLocation(program, addr(program, 0x00400123)))
.size()); .size());
@ -1041,7 +1035,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
addTextMapping(recorder3, text3, program); addTextMapping(recorder3, text3, program);
waitForSwing(); waitForSwing();
waitForPass(() -> { waitForPass(() -> {
assertEquals(2, mappingService assertEquals(2,
mappingService
.getOpenMappedLocations( .getOpenMappedLocations(
new ProgramLocation(program, addr(program, 0x00400123))) new ProgramLocation(program, addr(program, 0x00400123)))
.size()); .size());
@ -1094,7 +1089,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
addTextMapping(recorder3, text3, program); addTextMapping(recorder3, text3, program);
waitForSwing(); waitForSwing();
waitForPass(() -> { waitForPass(() -> {
assertEquals(2, mappingService assertEquals(2,
mappingService
.getOpenMappedLocations( .getOpenMappedLocations(
new ProgramLocation(program, addr(program, 0x00400123))) new ProgramLocation(program, addr(program, 0x00400123)))
.size()); .size());
@ -1548,8 +1544,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
DebuggerStaticMappingUtils.addMapping( DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(tb.trace, null, textRegion.getLifespan(), new DefaultTraceLocation(tb.trace, null, textRegion.getLifespan(),
textRegion.getMinAddress()), textRegion.getMinAddress()),
new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false);
false);
} }
} }
@ -1621,8 +1616,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
@Test @Test
public void testAddTraceBreakpointSetSleighThenMapThenSaveToProgramCopiesSleigh() public void testAddTraceBreakpointSetSleighThenMapThenSaveToProgramCopiesSleigh()
throws Throwable { throws Throwable {
DebuggerControlService editingService = DebuggerControlService editingService = addPlugin(tool, DebuggerControlServicePlugin.class);
addPlugin(tool, DebuggerControlServicePlugin.class);
// TODO: What if already mapped? // TODO: What if already mapped?
// Not sure I care about tb.setEmuSleigh() out of band // Not sure I care about tb.setEmuSleigh() out of band
@ -1637,8 +1631,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
try (Transaction tid = tb.startTransaction()) { try (Transaction tid = tb.startTransaction()) {
TraceBreakpoint bpt = tb.trace.getBreakpointManager() TraceBreakpoint bpt = tb.trace.getBreakpointManager()
.addBreakpoint("Processes[1].Breakpoints[0]", Lifespan.nowOn(0), .addBreakpoint("Processes[1].Breakpoints[0]", Lifespan.nowOn(0),
tb.addr(0x55550123), tb.addr(0x55550123), Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE),
Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE),
false /* emuEnabled defaults to true */, ""); false /* emuEnabled defaults to true */, "");
bpt.setEmuSleigh("r0=0xbeef;"); bpt.setEmuSleigh("r0=0xbeef;");
} }

View file

@ -32,12 +32,12 @@ import ghidra.util.datastruct.ListenerSet;
public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectModel { public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectModel {
public final Object lock = new Object(); public final Object lock = new Object();
public final Object cbLock = new Object(); public final Object cbLock = new Object();
protected final ExecutorService clientExecutor = protected final ExecutorService clientExecutor = Executors.newSingleThreadExecutor(
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() new BasicThreadFactory.Builder().namingPattern(getClass().getSimpleName() + "-thread-%d")
.namingPattern(getClass().getSimpleName() + "-thread-%d")
.build()); .build());
protected final ListenerSet<DebuggerModelListener> listeners = protected final ListenerSet<DebuggerModelListener> listeners =
new ListenerSet<>(DebuggerModelListener.class, clientExecutor); new ListenerSet<>(DebuggerModelListener.class, true);
protected SpiTargetObject root; protected SpiTargetObject root;
protected boolean rootAdded; protected boolean rootAdded;
@ -98,7 +98,10 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
return null; return null;
}); });
this.completedRoot.completeAsync(() -> root, clientExecutor); this.completedRoot.completeAsync(() -> root, clientExecutor);
listeners.fire.rootAdded(root);
clientExecutor.execute(() -> {
listeners.invoke().rootAdded(root);
});
} }
} }
@ -249,13 +252,11 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
} }
DefaultTargetObject<?, ?> dtoParent = (DefaultTargetObject<?, ?>) delegate; DefaultTargetObject<?, ?> dtoParent = (DefaultTargetObject<?, ?>) delegate;
if (PathUtils.isIndex(path)) { if (PathUtils.isIndex(path)) {
dtoParent.changeElements(List.of(PathUtils.getIndex(path)), List.of(), dtoParent.changeElements(List.of(PathUtils.getIndex(path)), List.of(), "Replaced");
"Replaced");
} }
else { else {
assert PathUtils.isName(path); assert PathUtils.isName(path);
dtoParent.changeAttributes(List.of(PathUtils.getKey(path)), Map.of(), dtoParent.changeAttributes(List.of(PathUtils.getKey(path)), Map.of(), "Replaced");
"Replaced");
} }
} }

View file

@ -304,6 +304,6 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
@Override @Override
public DebuggerModelListener broadcast() { public DebuggerModelListener broadcast() {
return model.listeners.fire; return model.listeners.invoke();
} }
} }

View file

@ -263,6 +263,6 @@ public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel {
} }
public DebuggerModelListener fire() { public DebuggerModelListener fire() {
return listeners.fire; return listeners.invoke();
} }
} }

View file

@ -141,14 +141,14 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
protected Set<DBTraceTimeViewport> viewports = new WeakHashCowSet<>(); protected Set<DBTraceTimeViewport> viewports = new WeakHashCowSet<>();
protected ListenerSet<DBTraceDirectChangeListener> directListeners = protected ListenerSet<DBTraceDirectChangeListener> directListeners =
new ListenerSet<>(DBTraceDirectChangeListener.class); new ListenerSet<>(DBTraceDirectChangeListener.class, true);
protected DBTraceVariableSnapProgramView programView; protected DBTraceVariableSnapProgramView programView;
protected Set<DBTraceVariableSnapProgramView> programViews = new WeakHashCowSet<>(); protected Set<DBTraceVariableSnapProgramView> programViews = new WeakHashCowSet<>();
protected Set<TraceProgramView> programViewsView = Collections.unmodifiableSet(programViews); protected Set<TraceProgramView> programViewsView = Collections.unmodifiableSet(programViews);
protected Map<Long, DBTraceProgramView> fixedProgramViews = new WeakValueHashCowMap<>(); protected Map<Long, DBTraceProgramView> fixedProgramViews = new WeakValueHashCowMap<>();
// NOTE: Can't pre-construct unmodifiableMap(fixedProgramViews), because values()' id changes // NOTE: Can't pre-construct unmodifiableMap(fixedProgramViews), because values()' id changes
protected ListenerSet<TraceProgramViewListener> viewListeners = protected ListenerSet<TraceProgramViewListener> viewListeners =
new ListenerSet<>(TraceProgramViewListener.class); new ListenerSet<>(TraceProgramViewListener.class, true);
public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer) public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer)
throws IOException, LanguageNotFoundException { throws IOException, LanguageNotFoundException {
@ -591,7 +591,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
super.fireEvent(ev); super.fireEvent(ev);
if (directListeners != null) { if (directListeners != null) {
// Some events fire during construction // 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); return new DBTraceProgramView(this, snap, baseCompilerSpec);
}); });
} }
viewListeners.fire.viewCreated(view); viewListeners.invoke().viewCreated(view);
return view; return view;
} }
@ -625,7 +625,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
view = new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec); view = new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
programViews.add(view); programViews.add(view);
} }
viewListeners.fire.viewCreated(view); viewListeners.invoke().viewCreated(view);
return view; return view;
} }

View file

@ -55,7 +55,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
*/ */
protected final List<Lifespan> ordered = new ArrayList<>(); protected final List<Lifespan> ordered = new ArrayList<>();
protected final MutableLifeSet spanSet = new DefaultLifeSet(); 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; protected long snap = 0;
@ -224,7 +224,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
} }
} }
assert !ordered.isEmpty(); assert !ordered.isEmpty();
changeListeners.fire.run(); changeListeners.invoke().run();
} }
@Override @Override

View file

@ -84,7 +84,7 @@ public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
double pos = double pos =
span * (e.getX() - colX) / myViewCol.getWidth() + fullRangeDouble.min(); 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 int savedViewColumn;
private final ForSeekMouseListener forSeekMouseListener = new ForSeekMouseListener(); 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) { public RangeCursorTableHeaderRenderer(N pos) {
this.pos = pos; this.pos = pos;

View file

@ -24,6 +24,7 @@ public class DefaultObservableCollection<E, L extends CollectionChangeListener<?
protected enum Change { protected enum Change {
ADDED, REMOVED, MODIFIED; ADDED, REMOVED, MODIFIED;
static Change then(Change one, Change two) { static Change then(Change one, Change two) {
if (one == null) { if (one == null) {
return two; return two;
@ -91,13 +92,13 @@ public class DefaultObservableCollection<E, L extends CollectionChangeListener<?
for (Map.Entry<E, Change> ent : changes.entrySet()) { for (Map.Entry<E, Change> ent : changes.entrySet()) {
switch (ent.getValue()) { switch (ent.getValue()) {
case ADDED: case ADDED:
listeners.fire.elementAdded(ent.getKey()); listeners.invoke().elementAdded(ent.getKey());
break; break;
case REMOVED: case REMOVED:
listeners.fire.elementRemoved(ent.getKey()); listeners.invoke().elementRemoved(ent.getKey());
break; break;
case MODIFIED: case MODIFIED:
listeners.fire.elementModified(ent.getKey()); listeners.invoke().elementModified(ent.getKey());
break; break;
} }
} }
@ -115,7 +116,7 @@ public class DefaultObservableCollection<E, L extends CollectionChangeListener<?
@Override @Override
public void close() { public void close() {
if (--aggregatorCount == 0) { if (--aggregatorCount == 0) {
l = listeners.fire; l = listeners.getProxy();
changes.fire(); changes.fire();
} }
} }
@ -130,8 +131,8 @@ public class DefaultObservableCollection<E, L extends CollectionChangeListener<?
protected DefaultObservableCollection(Collection<E> wrapped, Class<L> listenerClass) { protected DefaultObservableCollection(Collection<E> wrapped, Class<L> listenerClass) {
this.wrapped = wrapped; this.wrapped = wrapped;
this.listeners = new ListenerSet<>(listenerClass); this.listeners = new ListenerSet<>(listenerClass, true);
this.l = this.listeners.fire; this.l = this.listeners.getProxy();
} }
@Override @Override

View file

@ -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();
}
}
}

View file

@ -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();
}
}

View file

@ -16,6 +16,7 @@
package ghidra.util.datastruct; package ghidra.util.datastruct;
import java.lang.reflect.*; import java.lang.reflect.*;
import java.util.Objects;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -28,6 +29,9 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
*/ */
public class PrivatelyQueuedListener<P> { public class PrivatelyQueuedListener<P> {
private ListenerErrorHandler errorHandler =
DataStructureErrorHandlerFactory.createListenerErrorHandler();
protected class ListenerHandler implements InvocationHandler { protected class ListenerHandler implements InvocationHandler {
private static final Method OBJECT_HASHCODE; private static final Method OBJECT_HASHCODE;
static { static {
@ -55,10 +59,10 @@ public class PrivatelyQueuedListener<P> {
} }
catch (InvocationTargetException e) { catch (InvocationTargetException e) {
Throwable cause = e.getCause(); Throwable cause = e.getCause();
ListenerMap.reportError(out, cause); errorHandler.handleError(out, cause);
} }
catch (Throwable e) { catch (Throwable e) {
ListenerMap.reportError(out, e); errorHandler.handleError(out, e);
} }
}); });
return null; // Assumes void return type return null; // Assumes void return type
@ -98,16 +102,16 @@ public class PrivatelyQueuedListener<P> {
/** /**
* Create a new single-threaded privately-queued listener * Create a new single-threaded privately-queued listener
* *
* @see {@link #PrivatelyQueuedListener(Class, Executor, Object)}
* @param iface the interface of the listener * @param iface the interface of the listener
* @param threadNamePattern a pattern for naming the single thread * @param threadNamePattern a pattern for naming the single thread
* @param out the listener to receive the queued invocations * @param out the listener to receive the queued invocations
*/ */
public PrivatelyQueuedListener(Class<P> iface, String threadNamePattern, P out) { public PrivatelyQueuedListener(Class<P> iface, String threadNamePattern, P out) {
this(iface, this(iface, Executors.newSingleThreadExecutor(
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() new BasicThreadFactory.Builder().namingPattern(threadNamePattern).build()), out);
.namingPattern(threadNamePattern) }
.build()),
out); public void setErrorHandler(ListenerErrorHandler errorHandler) {
this.errorHandler = Objects.requireNonNull(errorHandler);
} }
} }

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -22,7 +22,7 @@ import java.util.*;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import ghidra.util.Msg; 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 * A class which handles exceptions that occur off of the main test thread. Exceptions can be
@ -41,13 +41,23 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
private static volatile boolean enabled = true; 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() { public static void registerHandler() {
SystemUtilities.runSwingLater(() -> { // Note: not sure why this is done on the Swing thread later. Seems like this could be done
// do this on the Swing thread // when this method is called, from any thread.
Swing.runLater(() -> {
Thread.setDefaultUncaughtExceptionHandler(new ConcurrentTestExceptionHandler()); 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) { public synchronized static void handle(Thread thread, Throwable t) {
if (!enabled) { if (!enabled) {
@ -89,26 +99,49 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
return StringUtils.containsAny(message, IGNORABLE_ERROR_MESSAGES); return StringUtils.containsAny(message, IGNORABLE_ERROR_MESSAGES);
} }
/**
* Clears all exceptions being tracked by this class
*/
public synchronized static void clear() { public synchronized static void clear() {
throwables.clear(); throwables.clear();
} }
/**
* Enables this class after a call to {@link #disable()} has been made
*/
public synchronized static void enable() { public synchronized static void enable() {
enabled = true; 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() { public synchronized static void disable() {
enabled = false; 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() { public synchronized static boolean isEnabled() {
return enabled; return enabled;
} }
/**
* Returns all exceptions tracked by this class
* @return all exceptions tracked by this class
*/
public static synchronized List<TestExceptionTracker> getExceptions() { public static synchronized List<TestExceptionTracker> getExceptions() {
return new ArrayList<>(throwables); 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() { public static synchronized boolean hasException() {
return !throwables.isEmpty(); return !throwables.isEmpty();
} }

View file

@ -38,14 +38,18 @@ class CopyOnReadWeakSet<T> extends WeakSet<T> {
} }
@Override @Override
public synchronized void add(T t) { public synchronized boolean add(T t) {
maybeWarnAboutAnonymousValue(t); maybeWarnAboutAnonymousValue(t);
boolean contains = weakHashStorage.containsKey(t);
weakHashStorage.put(t, null); weakHashStorage.put(t, null);
return !contains;
} }
@Override @Override
public synchronized void remove(T t) { public synchronized boolean remove(Object t) {
boolean contains = weakHashStorage.containsKey(t);
weakHashStorage.remove(t); weakHashStorage.remove(t);
return contains;
} }
@Override @Override
@ -64,7 +68,7 @@ class CopyOnReadWeakSet<T> extends WeakSet<T> {
} }
@Override @Override
public synchronized boolean contains(T t) { public synchronized boolean contains(Object t) {
return weakHashStorage.containsKey(t); return weakHashStorage.containsKey(t);
} }
@ -88,4 +92,32 @@ class CopyOnReadWeakSet<T> extends WeakSet<T> {
return createCopy().stream(); 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);
}
} }

View file

@ -52,38 +52,23 @@ class CopyOnWriteWeakSet<T> extends WeakSet<T> {
return IteratorUtils.unmodifiableIterator(weakHashStorage.keySet().iterator()); 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 @Override
public synchronized void addAll(Iterable<T> it) { public synchronized boolean add(T t) {
// 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) {
maybeWarnAboutAnonymousValue(t); maybeWarnAboutAnonymousValue(t);
WeakHashMap<T, T> newStorage = new WeakHashMap<>(weakHashStorage); WeakHashMap<T, T> newStorage = new WeakHashMap<>(weakHashStorage);
boolean contains = newStorage.containsKey(t);
newStorage.put(t, null); newStorage.put(t, null);
weakHashStorage = newStorage; weakHashStorage = newStorage;
return !contains;
} }
@Override @Override
public synchronized void remove(T t) { public synchronized boolean remove(Object t) {
WeakHashMap<T, T> newStorage = new WeakHashMap<>(weakHashStorage); WeakHashMap<T, T> newStorage = new WeakHashMap<>(weakHashStorage);
boolean contains = newStorage.containsKey(t);
newStorage.remove(t); newStorage.remove(t);
weakHashStorage = newStorage; weakHashStorage = newStorage;
return contains;
} }
@Override @Override
@ -107,7 +92,7 @@ class CopyOnWriteWeakSet<T> extends WeakSet<T> {
} }
@Override @Override
public boolean contains(T t) { public boolean contains(Object t) {
return weakHashStorage.containsKey(t); return weakHashStorage.containsKey(t);
} }
@ -120,4 +105,48 @@ class CopyOnWriteWeakSet<T> extends WeakSet<T> {
public String toString() { public String toString() {
return values().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;
}
} }

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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&lt;ActionListener&gt; 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);
}
}

View file

@ -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;
}
}
}

View file

@ -26,14 +26,18 @@ class ThreadUnsafeWeakSet<T> extends WeakSet<T> {
} }
@Override @Override
public void add(T t) { public boolean add(T t) {
maybeWarnAboutAnonymousValue(t); maybeWarnAboutAnonymousValue(t);
boolean contains = weakHashStorage.containsKey(t);
weakHashStorage.put(t, null); weakHashStorage.put(t, null);
return !contains;
} }
@Override @Override
public void remove(T t) { public boolean remove(Object t) {
boolean contains = weakHashStorage.containsKey(t);
weakHashStorage.remove(t); weakHashStorage.remove(t);
return contains;
} }
@Override @Override
@ -62,7 +66,7 @@ class ThreadUnsafeWeakSet<T> extends WeakSet<T> {
} }
@Override @Override
public boolean contains(T t) { public boolean contains(Object t) {
return weakHashStorage.containsKey(t); return weakHashStorage.containsKey(t);
} }
@ -76,4 +80,31 @@ class ThreadUnsafeWeakSet<T> extends WeakSet<T> {
return values().stream(); 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);
}
} }

View file

@ -36,16 +36,6 @@ public class WeakDataStructureFactory {
return new ThreadUnsafeWeakSet<>(); 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. * Use when mutations outweigh iterations.
* *

View file

@ -17,14 +17,13 @@ package ghidra.util.datastruct;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collection; import java.util.*;
import java.util.WeakHashMap;
import java.util.stream.Stream; import java.util.stream.Stream;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.SystemUtilities; 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 = private static final boolean WARN_ON_ANONYMOUS_VALUE =
SystemUtilities.isInDevelopmentMode() || SystemUtilities.isInTestingMode(); SystemUtilities.isInDevelopmentMode() || SystemUtilities.isInTestingMode();
@ -78,51 +77,47 @@ public abstract class WeakSet<T> implements Iterable<T> {
// Interface Methods // 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 * Add the given object to the set
* @param t the object to add * @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 * Remove the given object from the data structure
* @param t the object to remove * @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 * Returns true if the given object is in this data structure
* @param t the object * @param t the object
* @return true if the given object is in this data structure * @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 * Remove all elements from this data structure
*/ */
@Override
public abstract void clear(); public abstract void clear();
/** /**
* Return the number of objects contained within this data structure * Return the number of objects contained within this data structure
* @return the size * @return the size
*/ */
@Override
public abstract int size(); public abstract int size();
/** /**
* Return whether this data structure is empty * Return whether this data structure is empty
* @return whether this data structure is empty * @return whether this data structure is empty
*/ */
@Override
public abstract boolean isEmpty(); public abstract boolean isEmpty();
/** /**
@ -132,9 +127,36 @@ public abstract class WeakSet<T> implements Iterable<T> {
*/ */
public abstract Collection<T> values(); 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. * Returns a stream of the values of this collection.
* @return a stream of the values of this collection. * @return a stream of the values of this collection.
*/ */
@Override
public abstract Stream<T> stream(); public abstract Stream<T> stream();
} }

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -15,13 +15,11 @@
*/ */
package ghidra.util.datastruct; 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 java.util.*;
import org.junit.*; import org.junit.Test;
import generic.test.AbstractGenericTest; import generic.test.AbstractGenericTest;
@ -33,46 +31,6 @@ import generic.test.AbstractGenericTest;
*/ */
public class WeakSetTest extends AbstractGenericTest { 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 @Test
public void testAddAndRemove() { public void testAddAndRemove() {
WeakSet<String> weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet(); 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 @Test
public void testClear() { public void testClear() {
WeakSet<String> weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet(); WeakSet<String> weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet();
@ -125,35 +78,32 @@ public class WeakSetTest extends AbstractGenericTest {
weakSet.isEmpty()); weakSet.isEmpty());
} }
// /* @Test
// * Test method for 'ghidra.util.WeakSet.toArray()' public void testToArray() {
// */ WeakSet<String> weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet();
// public void testToArray() {
// WeakSet<String> weakSet = new WeakSet<String>(); String[] values = { "one", "two", "three" };
//
// String[] values = { "one", "two", "three" }; // test add
// for (String value : values) {
// // test add weakSet.add(value);
// 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()));
// assertTrue( "The weak set does not contain the correct number of " +
// "elements after calling add().", (values.length==weakSet.size()) ); Object[] valuesArray = weakSet.toArray();
//
// Object[] valuesArray = weakSet.toArray(); // check the array against our values
// assertTrue("The weak set returned a values array that is not the " +
// // check the array against our values "size as the number of values passed in.", (valuesArray.length == values.length));
// assertTrue( "The weak set returned a values array that is not the " +
// "size as the number of values passed in.", List<?> valuesList = new ArrayList<>(Arrays.asList(values));
// (valuesArray.length==values.length) ); for (Object element : valuesArray) {
// assertTrue("An element returned from the weak set was not " + "passed to the set.",
// List valuesList = new ArrayList( Arrays.asList( values ) ); valuesList.contains(element));
// 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 method for 'ghidra.util.WeakSet.getListeners()' * Test method for 'ghidra.util.WeakSet.getListeners()'
@ -187,44 +137,46 @@ public class WeakSetTest extends AbstractGenericTest {
} }
// Commented out because it was slow // Commented out because it was slow
// @Test
// public void testReferencesRemovedAfterCollection() { // public void testReferencesRemovedAfterCollection() {
// // create some references and hold on to them to make sure that // // create some references and hold on to them to make sure that
// // they are not collected and stay in the set // // they are not collected and stay in the set
// WeakSet<ActionListener> weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet(); // WeakSet<ActionListener> weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet();
// //
// ActionListener[] values = // ActionListener[] values = new ActionListener[] { new ActionListenerAdapter(),
// new ActionListener[] { new ActionListenerAdapter(), new ActionListenerAdapter(), // new ActionListenerAdapter(), new ActionListenerAdapter(), new ActionListenerAdapter() };
// new ActionListenerAdapter(), new ActionListenerAdapter() };
// //
// // test add // // test add
// for (int i = 0; i < values.length; i++) { // for (ActionListener value : values) {
// weakSet.add(values[i]); // weakSet.add(value);
// } // }
// //
// assertTrue("The weak set does not contain the correct number of " // assertTrue("The weak set does not contain the correct number of " +
// + "elements after calling add().", (values.length == weakSet.size())); // "elements after calling add().", (values.length == weakSet.size()));
// //
// // now release *all* those references // // now release *all* those references
// for (int i = 0; i < values.length; i++) {
// values[i] = null;
// }
// values = null; // values = null;
// //
// // force garbage collection // // force garbage collection
// forceGarbageCollection(); // forceGarbageCollection();
// //
// // make sure that the unreferenced objects are removed from the set // // make sure that the unreferenced objects are removed from the set
// assertTrue("The elements added to the weak set were not removed " // assertTrue("The elements added to the weak set were not removed " +
// + "when they were no longer referenced.", (0 == weakSet.size())); // "when they were no longer referenced.", (0 == weakSet.size()));
// //
// // now try the test again while only deleting some of the values // // now try the test again while only deleting some of the values
// values = // values = new ActionListener[] { new ActionListenerAdapter(), new ActionListenerAdapter(),
// new ActionListener[] { new ActionListenerAdapter(), new ActionListenerAdapter(),
// new ActionListenerAdapter(), new ActionListenerAdapter() }; // new ActionListenerAdapter(), new ActionListenerAdapter() };
// //
// for (int i = 0; i < values.length; i++) { // for (ActionListener value : values) {
// weakSet.add(values[i]); // weakSet.add(value);
// } // }
// //
// assertTrue("The weak set does not contain the correct number of " // assertTrue("The weak set does not contain the correct number of " +
// + "elements after calling add().", (values.length == weakSet.size())); // "elements after calling add().", (values.length == weakSet.size()));
// //
// // null out some values // // null out some values
// values[0] = null; // values[0] = null;
@ -234,19 +186,19 @@ public class WeakSetTest extends AbstractGenericTest {
// forceGarbageCollection(); // forceGarbageCollection();
// //
// // make sure that the unreferenced objects are removed from the set // // make sure that the unreferenced objects are removed from the set
// assertTrue("The elements added to the weak set were not removed " // assertTrue("The elements added to the weak set were not removed " +
// + "when they were no longer referenced.", (2 == weakSet.size())); // "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() { // private void forceGarbageCollection() {
// waitForSwing(); //
// System.gc(); // System.gc();
// System.gc(); // System.gc();
// try { // try {