diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgDetachCommand.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgDetachCommand.java index 43fbad9c24..ffd5a22ded 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgDetachCommand.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/cmd/DbgDetachCommand.java @@ -42,7 +42,7 @@ public class DbgDetachCommand extends AbstractDbgCommand { manager.fireThreadExited(t.getId(), process, pending); t.remove(); } - manager.getEventListeners().fire.processRemoved(process.getId(), DbgCause.Causes.UNCLAIMED); + manager.getEventListeners().invoke().processRemoved(process.getId(), DbgCause.Causes.UNCLAIMED); return null; } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgDebugInputCallbacks.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgDebugInputCallbacks.java index 96e8c8f4ad..2f631c27a6 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgDebugInputCallbacks.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgDebugInputCallbacks.java @@ -29,7 +29,7 @@ public class DbgDebugInputCallbacks implements DebugInputCallbacks { @Override public void startInput(long bufsize) { - manager.getEventListeners().fire.promptChanged(">>>"); + manager.getEventListeners().invoke().promptChanged(">>>"); CompletableFuture cf = new CompletableFuture(); try { manager.setContinuation(cf); diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java index f8dfc9b9a9..2ae51b93cc 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgManagerImpl.java @@ -206,7 +206,7 @@ public class DbgManagerImpl implements DbgManager { private final Map, DebugStatus> statusMap = new LinkedHashMap<>(); private final Map statusByNameMap = new LinkedHashMap<>(); private final ListenerSet listenersEvent = - new ListenerSet<>(DbgEventsListener.class); + new ListenerSet<>(DbgEventsListener.class, true); private DebugEventInformation lastEventInformation; private DbgSession currentSession; @@ -251,8 +251,8 @@ public class DbgManagerImpl implements DbgManager { thread.add(); if (fire) { Causes cause = DbgCause.Causes.UNCLAIMED; - getEventListeners().fire.threadCreated(thread, cause); - getEventListeners().fire.threadSelected(thread, null, cause); + getEventListeners().invoke().threadCreated(thread, cause); + getEventListeners().invoke().threadSelected(thread, null, cause); } return threads.get(id); } @@ -294,7 +294,7 @@ public class DbgManagerImpl implements DbgManager { for (DebugThreadId tid : toRemove) { removeThread(tid); } - getEventListeners().fire.processRemoved(id, cause); + getEventListeners().invoke().processRemoved(id, cause); } } @@ -303,7 +303,7 @@ public class DbgManagerImpl implements DbgManager { * * @param process the process that now has focus * @param cause the cause of the focus change - * @param fire signal listeners + * @param forEach() signal listeners * @return success status */ @Override @@ -326,7 +326,7 @@ public class DbgManagerImpl implements DbgManager { DbgProcessImpl process = new DbgProcessImpl(this, id, pid, name); process.add(); if (fire) { - getEventListeners().fire.processAdded(process, DbgCause.Causes.UNCLAIMED); + getEventListeners().invoke().processAdded(process, DbgCause.Causes.UNCLAIMED); } return processes.get(id); } @@ -344,7 +344,7 @@ public class DbgManagerImpl implements DbgManager { if (sessions.remove(id) == null) { throw new IllegalArgumentException("There is no session with id " + id); } - getEventListeners().fire.sessionRemoved(id, cause); + getEventListeners().invoke().sessionRemoved(id, cause); } } @@ -366,7 +366,7 @@ public class DbgManagerImpl implements DbgManager { DbgSessionImpl session = new DbgSessionImpl(this, id); session.add(); if (fire) { - getEventListeners().fire.sessionAdded(session, DbgCause.Causes.UNCLAIMED); + getEventListeners().invoke().sessionAdded(session, DbgCause.Causes.UNCLAIMED); } } return sessions.get(id); @@ -748,8 +748,8 @@ public class DbgManagerImpl implements DbgManager { DebugBreakpoint bp = evt.getInfo(); DbgBreakpointInfo info = new DbgBreakpointInfo(bp, getEventProcess(), getEventThread()); - getEventListeners().fire.threadSelected(eventThread, null, evt.getCause()); - getEventListeners().fire.breakpointHit(info, evt.getCause()); + getEventListeners().invoke().threadSelected(eventThread, null, evt.getCause()); + getEventListeners().invoke().breakpointHit(info, evt.getCause()); String key = Integer.toHexString(bp.getId()); if (statusByNameMap.containsKey(key)) { @@ -766,8 +766,8 @@ public class DbgManagerImpl implements DbgManager { * @return retval handling/break status */ protected DebugStatus processException(DbgExceptionEvent evt, Void v) { - getEventListeners().fire.eventSelected(evt, evt.getCause()); - getEventListeners().fire.threadSelected(eventThread, null, evt.getCause()); + getEventListeners().invoke().eventSelected(evt, evt.getCause()); + getEventListeners().invoke().threadSelected(eventThread, null, evt.getCause()); DebugExceptionRecord64 info = evt.getInfo(); String key = Integer.toHexString(info.code); @@ -789,9 +789,9 @@ public class DbgManagerImpl implements DbgManager { DbgProcessImpl process = getCurrentProcess(); DbgThreadImpl thread = getThreadFromDebugProcessInfo(process, evt.getInfo()); - getEventListeners().fire.eventSelected(evt, evt.getCause()); - getEventListeners().fire.threadCreated(thread, DbgCause.Causes.UNCLAIMED); - getEventListeners().fire.threadSelected(thread, null, evt.getCause()); + getEventListeners().invoke().eventSelected(evt, evt.getCause()); + getEventListeners().invoke().threadCreated(thread, DbgCause.Causes.UNCLAIMED); + getEventListeners().invoke().threadSelected(thread, null, evt.getCause()); String key = eventId.id(); if (statusByNameMap.containsKey(key)) { @@ -815,8 +815,8 @@ public class DbgManagerImpl implements DbgManager { thread.remove(); } process.threadExited(eventId); - getEventListeners().fire.eventSelected(evt, evt.getCause()); - getEventListeners().fire.threadExited(eventId, process, evt.getCause()); + getEventListeners().invoke().eventSelected(evt, evt.getCause()); + getEventListeners().invoke().threadExited(eventId, process, evt.getCause()); String key = eventId.id(); if (statusByNameMap.containsKey(key)) { @@ -842,7 +842,7 @@ public class DbgManagerImpl implements DbgManager { //currentThread = evt.getThread(); currentThread.setState(evt.getState(), evt.getCause(), evt.getReason()); - getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause()); + getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause()); String key = eventId.id(); if (statusByNameMap.containsKey(key)) { @@ -861,9 +861,9 @@ public class DbgManagerImpl implements DbgManager { protected DebugStatus processProcessCreated(DbgProcessCreatedEvent evt, Void v) { DebugProcessInfo info = evt.getInfo(); DbgProcessImpl proc = getProcessFromDebugProcessInfo(info); - getEventListeners().fire.eventSelected(evt, evt.getCause()); - getEventListeners().fire.processAdded(proc, evt.getCause()); - getEventListeners().fire.processSelected(proc, evt.getCause()); + getEventListeners().invoke().eventSelected(evt, evt.getCause()); + getEventListeners().invoke().processAdded(proc, evt.getCause()); + getEventListeners().invoke().processSelected(proc, evt.getCause()); getThreadFromDebugProcessInfo(proc, info.initialThreadInfo); //getEventListeners().fire.threadCreated(thread, evt.getCause()); @@ -892,9 +892,9 @@ public class DbgManagerImpl implements DbgManager { DbgProcessImpl process = getCurrentProcess(); process.setExitCode(Long.valueOf(evt.getInfo())); - getEventListeners().fire.eventSelected(evt, evt.getCause()); - getEventListeners().fire.threadExited(eventId, process, evt.getCause()); - getEventListeners().fire.processExited(process, evt.getCause()); + getEventListeners().invoke().eventSelected(evt, evt.getCause()); + getEventListeners().invoke().threadExited(eventId, process, evt.getCause()); + getEventListeners().invoke().processExited(process, evt.getCause()); for (DebugBreakpoint bpt : getBreakpoints()) { breaksById.remove(bpt.getId()); @@ -903,7 +903,7 @@ public class DbgManagerImpl implements DbgManager { thread.remove(); } process.remove(evt.getCause()); - getEventListeners().fire.processRemoved(process.getId(), evt.getCause()); + getEventListeners().invoke().processRemoved(process.getId(), evt.getCause()); String key = process.getId().id(); if (statusByNameMap.containsKey(key)) { @@ -923,7 +923,7 @@ public class DbgManagerImpl implements DbgManager { DebugThreadId eventId = updateState(); currentProcess = evt.getProcess(); - getEventListeners().fire.processSelected(currentProcess, evt.getCause()); + getEventListeners().invoke().processSelected(currentProcess, evt.getCause()); String key = eventId.id(); if (statusByNameMap.containsKey(key)) { @@ -944,8 +944,8 @@ public class DbgManagerImpl implements DbgManager { DbgProcessImpl process = getCurrentProcess(); DebugModuleInfo info = evt.getInfo(); process.moduleLoaded(info); - getEventListeners().fire.eventSelected(evt, evt.getCause()); - getEventListeners().fire.moduleLoaded(process, info, evt.getCause()); + getEventListeners().invoke().eventSelected(evt, evt.getCause()); + getEventListeners().invoke().moduleLoaded(process, info, evt.getCause()); String key = info.getModuleName(); if (statusByNameMap.containsKey(key)) { @@ -966,8 +966,8 @@ public class DbgManagerImpl implements DbgManager { DbgProcessImpl process = getCurrentProcess(); DebugModuleInfo info = evt.getInfo(); process.moduleUnloaded(info); - getEventListeners().fire.eventSelected(evt, evt.getCause()); - getEventListeners().fire.moduleUnloaded(process, info, evt.getCause()); + getEventListeners().invoke().eventSelected(evt, evt.getCause()); + getEventListeners().invoke().moduleUnloaded(process, info, evt.getCause()); String key = info.getModuleName(); if (statusByNameMap.containsKey(key)) { @@ -1012,7 +1012,7 @@ public class DbgManagerImpl implements DbgManager { //System.err.println("RUNNING " + id); dbgState = DbgState.RUNNING; // NB: Needed by GADP variants, but not IN-VM - getEventListeners().fire.memoryChanged(currentProcess, 0L, 0, + getEventListeners().invoke().memoryChanged(currentProcess, 0L, 0, evt.getCause()); processEvent(new DbgRunningEvent(eventThread.getId())); } @@ -1056,7 +1056,7 @@ public class DbgManagerImpl implements DbgManager { if (key.value() == id) { DbgThread thread = getThread(key); if (thread != null) { - getEventListeners().fire.threadSelected(thread, null, evt.getCause()); + getEventListeners().invoke().threadSelected(thread, null, evt.getCause()); } processEvent(new DbgPromptChangedEvent(getControl().getPromptText())); break; @@ -1080,7 +1080,7 @@ public class DbgManagerImpl implements DbgManager { DebugThreadId eventId = updateState(); currentSession = evt.getSession(); - getEventListeners().fire.sessionSelected(currentSession, evt.getCause()); + getEventListeners().invoke().sessionSelected(currentSession, evt.getCause()); String key = eventId.id(); if (statusByNameMap.containsKey(key)) { @@ -1118,23 +1118,23 @@ public class DbgManagerImpl implements DbgManager { protected void processDebuggeeStateChanged(DbgDebuggeeStateChangeEvent evt, Void v) { if (evt.getFlags().contains(ChangeDebuggeeState.DATA)) { - getEventListeners().fire.memoryChanged(currentProcess, 0L, 0, evt.getCause()); + getEventListeners().invoke().memoryChanged(currentProcess, 0L, 0, evt.getCause()); } } protected void processSystemErrorEvent(DbgSystemErrorEvent evt, Void v) { - getEventListeners().fire.eventSelected(evt, evt.getCause()); + getEventListeners().invoke().eventSelected(evt, evt.getCause()); String error = "SystemError " + evt.getError() + ":" + evt.getLevel(); - getEventListeners().fire.consoleOutput(error, 0); + getEventListeners().invoke().consoleOutput(error, 0); } protected void processConsoleOutput(DbgConsoleOutputEvent evt, Void v) { - getEventListeners().fire.eventSelected(evt, evt.getCause()); - getEventListeners().fire.consoleOutput(evt.getInfo(), evt.getMask()); + getEventListeners().invoke().eventSelected(evt, evt.getCause()); + getEventListeners().invoke().consoleOutput(evt.getInfo(), evt.getMask()); } protected void processPromptChanged(DbgPromptChangedEvent evt, Void v) { - getEventListeners().fire.promptChanged(evt.getPrompt()); + getEventListeners().invoke().promptChanged(evt.getPrompt()); } /** @@ -1215,7 +1215,7 @@ public class DbgManagerImpl implements DbgManager { @Internal public void doBreakpointCreated(DbgBreakpointInfo newInfo, DbgCause cause) { addKnownBreakpoint(newInfo, true); - getEventListeners().fire.breakpointCreated(newInfo, cause); + getEventListeners().invoke().breakpointCreated(newInfo, cause); } /** @@ -1227,7 +1227,7 @@ public class DbgManagerImpl implements DbgManager { @Internal public void doBreakpointModified(DbgBreakpointInfo newInfo, DbgCause cause) { DbgBreakpointInfo oldInfo = addKnownBreakpoint(newInfo, true); - getEventListeners().fire.breakpointModified(newInfo, oldInfo, cause); + getEventListeners().invoke().breakpointModified(newInfo, oldInfo, cause); } /** @@ -1242,7 +1242,7 @@ public class DbgManagerImpl implements DbgManager { if (oldInfo == null) { return; } - getEventListeners().fire.breakpointDeleted(oldInfo, cause); + getEventListeners().invoke().breakpointDeleted(oldInfo, cause); oldInfo.dispose(); } @@ -1251,7 +1251,7 @@ public class DbgManagerImpl implements DbgManager { if (Objects.equals(newInfo, oldInfo)) { return; } - getEventListeners().fire.breakpointModified(newInfo, oldInfo, cause); + getEventListeners().invoke().breakpointModified(newInfo, oldInfo, cause); } @Internal @@ -1637,7 +1637,7 @@ public class DbgManagerImpl implements DbgManager { } public void fireThreadExited(DebugThreadId id, DbgProcessImpl process, DbgCause cause) { - getEventListeners().fire.threadExited(id, process, cause); + getEventListeners().invoke().threadExited(id, process, cause); } @Override @@ -1771,7 +1771,7 @@ public class DbgManagerImpl implements DbgManager { if (mirror != null) { mirror.setOffset(currentProcess.getOffset()); currentProcess = eventProcess = mirror; - getEventListeners().fire.processSelected(eventProcess, Causes.UNCLAIMED); + getEventListeners().invoke().processSelected(eventProcess, Causes.UNCLAIMED); } }); } @@ -1786,7 +1786,7 @@ public class DbgManagerImpl implements DbgManager { if (mirror != null) { mirror.setOffset(currentThread.getOffset()); currentThread = eventThread = mirror; - getEventListeners().fire.threadSelected(eventThread, null, Causes.UNCLAIMED); + getEventListeners().invoke().threadSelected(eventThread, null, Causes.UNCLAIMED); } }); } @@ -1795,7 +1795,7 @@ public class DbgManagerImpl implements DbgManager { eventProcess = getProcessComputeIfAbsent(epid, so.getCurrentProcessSystemId(), so.getCurrentProcessExecutableName(), true); currentThread = eventThread = getThreadComputeIfAbsent(etid, (DbgProcessImpl) eventProcess, so.getCurrentThreadSystemId(), false); - getEventListeners().fire.threadSelected(eventThread, null, Causes.UNCLAIMED); + getEventListeners().invoke().threadSelected(eventThread, null, Causes.UNCLAIMED); } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgModuleImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgModuleImpl.java index 87e5b6c37e..129943da09 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgModuleImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgModuleImpl.java @@ -58,7 +58,7 @@ public class DbgModuleImpl implements DbgModule { */ public void add() { process.addModule(this); - manager.getEventListeners().fire.moduleLoaded(process, info, Causes.UNCLAIMED); + manager.getEventListeners().invoke().moduleLoaded(process, info, Causes.UNCLAIMED); } /** @@ -66,7 +66,7 @@ public class DbgModuleImpl implements DbgModule { */ public void remove() { process.removeModule(name); - manager.getEventListeners().fire.moduleUnloaded(process, info, Causes.UNCLAIMED); + manager.getEventListeners().invoke().moduleUnloaded(process, info, Causes.UNCLAIMED); } @Override diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgThreadImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgThreadImpl.java index ba47864e13..229c5f52cf 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgThreadImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/manager/impl/DbgThreadImpl.java @@ -93,7 +93,7 @@ public class DbgThreadImpl implements DbgThread { //manager.getEventListeners().fire.threadCreated(this, DbgCause.Causes.UNCLAIMED); process.addThread(this); state.addChangeListener((oldState, newState, pair) -> { - this.manager.getEventListeners().fire.threadStateChanged(this, newState, pair.cause, + this.manager.getEventListeners().invoke().threadStateChanged(this, newState, pair.cause, pair.reason); }); } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetBreakpointSpec.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetBreakpointSpec.java index 4bf058a0a3..77b5ff9049 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetBreakpointSpec.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetBreakpointSpec.java @@ -174,12 +174,12 @@ public interface DbgModelTargetBreakpointSpec extends // /** * Update the enabled field - * + * *

* This does not actually toggle the breakpoint. It just updates the field and calls the proper * listeners. To actually toggle the breakpoint, use {@link #toggle(boolean)} instead, which if * effective, should eventually cause this method to be called. - * + * * @param enabled true if enabled, false if disabled * @param reason a description of the cause (not really used, yet) */ @@ -207,8 +207,8 @@ public interface DbgModelTargetBreakpointSpec extends // public default void breakpointHit() { DbgModelTargetThread targetThread = getParentProcess().getThreads().getTargetThread(getManager().getEventThread()); - getActions().fire.breakpointHit((DbgModelTargetBreakpointSpec) getProxy(), targetThread, - null, this); + getActions().invoke() + .breakpointHit((DbgModelTargetBreakpointSpec) getProxy(), targetThread, null, this); } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetRegisterBank.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetRegisterBank.java index edb8157a52..b3ecf55a02 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetRegisterBank.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetRegisterBank.java @@ -124,7 +124,7 @@ public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, Target getParentThread().getThread().writeRegisters(toWrite).handle(seq::next); // TODO: Should probably filter only effective and normalized writes in the callback }).then(seq -> { - manager.getEventListeners().fire.threadStateChanged(thread, thread.getState(), + manager.getEventListeners().invoke().threadStateChanged(thread, thread.getState(), DbgCause.Causes.UNCLAIMED, DbgReason.Reasons.NONE); broadcast().registersUpdated(getProxy(), values); seq.exit(); diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java index 2dbdf3434f..f0e5c5f63d 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java @@ -110,7 +110,7 @@ public class DbgModelImpl extends AbstractDbgModel implements DebuggerObjectMode @Override public void terminate() throws IOException { - listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); + listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL); root.invalidateSubtree(root, "Dbgeng is terminating"); dbg.terminate(); } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetBreakpointSpecImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetBreakpointSpecImpl.java index 36d5930e07..d3ea892af2 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetBreakpointSpecImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetBreakpointSpecImpl.java @@ -15,7 +15,8 @@ */ package agent.dbgeng.model.impl; -import java.util.*; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import agent.dbgeng.manager.breakpoint.DbgBreakpointInfo; @@ -27,33 +28,21 @@ import ghidra.dbg.target.TargetBreakpointSpec; import ghidra.dbg.target.schema.TargetAttributeType; import ghidra.dbg.target.schema.TargetObjectSchemaInfo; import ghidra.dbg.util.PathUtils; -import ghidra.util.datastruct.ListenerMap.ListenerEntry; import ghidra.util.datastruct.ListenerSet; -@TargetObjectSchemaInfo( - name = "BreakpointSpec", - attributes = { // - @TargetAttributeType( // +@TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = { // + @TargetAttributeType( // name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, // type = DbgModelTargetBreakpointContainerImpl.class), // - @TargetAttributeType( // + @TargetAttributeType( // name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, // type = DbgModelTargetBreakpointSpecImpl.class), // - @TargetAttributeType( - name = DbgModelTargetBreakpointSpecImpl.BPT_TYPE_ATTRIBUTE_NAME, - type = String.class), // - @TargetAttributeType( - name = DbgModelTargetBreakpointSpecImpl.BPT_DISP_ATTRIBUTE_NAME, - type = String.class), // - @TargetAttributeType( - name = DbgModelTargetBreakpointSpecImpl.BPT_PENDING_ATTRIBUTE_NAME, - type = String.class), // - @TargetAttributeType( - name = DbgModelTargetBreakpointSpecImpl.BPT_TIMES_ATTRIBUTE_NAME, - type = Integer.class), // - @TargetAttributeType(type = Void.class) // - }, - canonicalContainer = true) + @TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_TYPE_ATTRIBUTE_NAME, type = String.class), // + @TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_DISP_ATTRIBUTE_NAME, type = String.class), // + @TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_PENDING_ATTRIBUTE_NAME, type = String.class), // + @TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_TIMES_ATTRIBUTE_NAME, type = Integer.class), // + @TargetAttributeType(type = Void.class) // +}, canonicalContainer = true) public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl implements DbgModelTargetBreakpointSpec { @@ -69,28 +58,21 @@ public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl protected boolean enabled; public void changeAttributeSet(String reason) { - this.changeAttributes(List.of(), List.of(), Map.of( - DISPLAY_ATTRIBUTE_NAME, "[" + info.getNumber() + "] " + info.getExpression(), - RANGE_ATTRIBUTE_NAME, doGetRange(), - SPEC_ATTRIBUTE_NAME, this, - EXPRESSION_ATTRIBUTE_NAME, info.getExpression(), - KINDS_ATTRIBUTE_NAME, getKinds(), + this.changeAttributes(List.of(), List.of(), + Map.of(DISPLAY_ATTRIBUTE_NAME, "[" + info.getNumber() + "] " + info.getExpression(), + RANGE_ATTRIBUTE_NAME, doGetRange(), SPEC_ATTRIBUTE_NAME, this, + EXPRESSION_ATTRIBUTE_NAME, info.getExpression(), KINDS_ATTRIBUTE_NAME, getKinds(), - BPT_TYPE_ATTRIBUTE_NAME, info.getType().name(), - BPT_DISP_ATTRIBUTE_NAME, info.getDisp().name(), - BPT_PENDING_ATTRIBUTE_NAME, info.getPending(), - BPT_TIMES_ATTRIBUTE_NAME, info.getTimes()), + BPT_TYPE_ATTRIBUTE_NAME, info.getType().name(), BPT_DISP_ATTRIBUTE_NAME, + info.getDisp().name(), BPT_PENDING_ATTRIBUTE_NAME, info.getPending(), + BPT_TIMES_ATTRIBUTE_NAME, info.getTimes()), reason); } + // Use strong references on actions + // The values may be weak, but the keys, which are the same objects, are strong private final ListenerSet actions = - new ListenerSet<>(TargetBreakpointAction.class) { - // Use strong references on actions - // The values may be weak, but the keys, which are the same objects, are strong - protected Map> createMap() { - return new LinkedHashMap<>(); - } - }; + new ListenerSet<>(TargetBreakpointAction.class, false); public DbgModelTargetBreakpointSpecImpl(DbgModelTargetBreakpointContainer breakpoints, DbgBreakpointInfo info) { @@ -128,11 +110,11 @@ public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl /** * Update the enabled field - * + * * This does not actually toggle the breakpoint. It just updates the field and calls the proper * listeners. To actually toggle the breakpoint, use {@link #toggle(boolean)} instead, which if * effective, should eventually cause this method to be called. - * + * * @param enabled true if enabled, false if disabled * @param reason a description of the cause (not really used, yet) */ diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionImpl.java index b51bcd3f2f..5c1869c54a 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetExceptionImpl.java @@ -109,8 +109,10 @@ public class DbgModelTargetExceptionImpl extends DbgModelTargetObjectImpl .setFocus(this); changeAttributes(List.of(), List.of(), Map.of( // MODIFIED_ATTRIBUTE_NAME, true), "Refreshed"); - manager.getEventListeners().fire.consoleOutput( - "Exception " + filter.getExceptionCode() + " : " + filter.getName() + "\n", 0); + manager.getEventListeners() + .invoke() + .consoleOutput("Exception " + filter.getExceptionCode() + " : " + + filter.getName() + "\n", 0); } } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterContainerImpl.java index 74831c977b..cff5f3dbe5 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterContainerImpl.java @@ -162,7 +162,7 @@ public class DbgModelTargetRegisterContainerImpl extends DbgModelTargetObjectImp return thread.writeRegisters(toWrite); // TODO: Should probably filter only effective and normalized writes in the callback }).thenAccept(__ -> { - manager.getEventListeners().fire.threadStateChanged(thread, thread.getState(), + manager.getEventListeners().invoke().threadStateChanged(thread, thread.getState(), DbgCause.Causes.UNCLAIMED, DbgReason.Reasons.NONE); broadcast().registersUpdated(getProxy(), values); })); diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2Impl.java b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2Impl.java index 149c6d6175..ab1363e515 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2Impl.java +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2Impl.java @@ -121,7 +121,7 @@ public class DbgModel2Impl extends AbstractDbgModel @Override public void terminate() throws IOException { - listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); + listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL); root.invalidateSubtree(root, "Dbgmodel is terminating"); dbg.terminate(); } diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DelegateDbgModel2TargetObject.java b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DelegateDbgModel2TargetObject.java index 4c64f277e4..f4c121f037 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DelegateDbgModel2TargetObject.java +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DelegateDbgModel2TargetObject.java @@ -31,7 +31,6 @@ import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.target.*; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointAction; import ghidra.dbg.util.PathUtils; -import ghidra.util.datastruct.ListenerMap.ListenerEntry; import ghidra.util.datastruct.ListenerSet; public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl implements // @@ -69,79 +68,81 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp } } - protected static Class lookupWrapperType(String type, String parentName) { + protected static Class lookupWrapperType(String type, + String parentName) { switch (type) { - case "Available": - return DbgModelTargetAvailableContainer.class; - case "Sessions": - return DbgModelTargetSessionContainer.class; - case "Processes": - return DbgModelTargetProcessContainer.class; - case "Threads": - return DbgModelTargetThreadContainer.class; - case "Modules": - return DbgModelTargetModuleContainer.class; - case "Frames": - return DbgModelTargetStack.class; - case "Registers": - return DbgModelTargetRegisterContainer.class; - case "Attributes": - return DbgModelTargetSessionAttributes.class; - case "Breakpoints": - return DbgModelTargetBreakpointContainer.class; - case "cursession": - return DbgModelTargetSession.class; - case "curprocess": - return DbgModelTargetProcess.class; - case "curthread": - return DbgModelTargetThread.class; - case "curframe": - return DbgModelTargetStackFrame.class; - case "User": - return DbgModelTargetRegisterBank.class; - case "TTD": - return DbgModelTargetTTD.class; - case "Debug": - return DbgModelTargetDebugContainer.class; + case "Available": + return DbgModelTargetAvailableContainer.class; + case "Sessions": + return DbgModelTargetSessionContainer.class; + case "Processes": + return DbgModelTargetProcessContainer.class; + case "Threads": + return DbgModelTargetThreadContainer.class; + case "Modules": + return DbgModelTargetModuleContainer.class; + case "Frames": + return DbgModelTargetStack.class; + case "Registers": + return DbgModelTargetRegisterContainer.class; + case "Attributes": + return DbgModelTargetSessionAttributes.class; + case "Breakpoints": + return DbgModelTargetBreakpointContainer.class; + case "cursession": + return DbgModelTargetSession.class; + case "curprocess": + return DbgModelTargetProcess.class; + case "curthread": + return DbgModelTargetThread.class; + case "curframe": + return DbgModelTargetStackFrame.class; + case "User": + return DbgModelTargetRegisterBank.class; + case "TTD": + return DbgModelTargetTTD.class; + case "Debug": + return DbgModelTargetDebugContainer.class; } if (parentName != null) { switch (parentName) { - case "Available": - return DbgModelTargetAvailable.class; - case "Sessions": - return DbgModelTargetSession.class; - case "Processes": - return DbgModelTargetProcess.class; - case "Threads": - return DbgModelTargetThread.class; - case "Modules": - return DbgModelTargetModule.class; - case "Frames": - return DbgModelTargetStackFrame.class; - case "Breakpoints": - return DbgModelTargetBreakpointSpec.class; - // case "Registers": - // return DbgModelTargetRegisterBank.class; - case "FloatingPoint": - case "Kernel": - case "SIMD": - case "VFP": - case "User": - return DbgModelTargetRegister.class; + case "Available": + return DbgModelTargetAvailable.class; + case "Sessions": + return DbgModelTargetSession.class; + case "Processes": + return DbgModelTargetProcess.class; + case "Threads": + return DbgModelTargetThread.class; + case "Modules": + return DbgModelTargetModule.class; + case "Frames": + return DbgModelTargetStackFrame.class; + case "Breakpoints": + return DbgModelTargetBreakpointSpec.class; + // case "Registers": + // return DbgModelTargetRegisterBank.class; + case "FloatingPoint": + case "Kernel": + case "SIMD": + case "VFP": + case "User": + return DbgModelTargetRegister.class; } } return null; } - public static DbgModelTargetObject makeProxy(DbgModel2Impl model, DbgModelTargetObject parent, String key, - ModelObject object) { + public static DbgModelTargetObject makeProxy(DbgModel2Impl model, DbgModelTargetObject parent, + String key, ModelObject object) { List> mixins = new ArrayList<>(); String lkey = key; String pname = parent.getName(); if (object.getKind().equals(ModelObjectKind.OBJECT_METHOD)) { mixins.add(DbgModelTargetMethod.class); - } else { + } + else { Class mixin = lookupWrapperType(lkey, pname); if (mixin != null) { mixins.add(mixin); @@ -161,12 +162,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp private boolean breakpointEnabled; private final ListenerSet breakpointActions = - new ListenerSet<>(TargetBreakpointAction.class) { - // Use strong references on actions - protected Map> createMap() { - return new LinkedHashMap<>(); - } - }; + new ListenerSet<>(TargetBreakpointAction.class, false); // Extending DefaultTargetObject may spare you from listeners, elements, and // attributes @@ -175,8 +171,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp // any other fields you need to support your impl - public DelegateDbgModel2TargetObject(DbgModel2Impl model, DbgModelTargetObject parent, String key, - ModelObject modelObject, List> mixins) { + public DelegateDbgModel2TargetObject(DbgModel2Impl model, DbgModelTargetObject parent, + String key, ModelObject modelObject, List> mixins) { super(model, mixins, model, parent.getProxy(), key, getHintForObject(modelObject)); // System.err.println(this); this.state = new ProxyState(model, modelObject); @@ -198,8 +194,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp if (mixin != null) { mixins.add(mixin); } - DelegateDbgModel2TargetObject delegate = new DelegateDbgModel2TargetObject(getModel(), p, key, modelObject, - mixins); + DelegateDbgModel2TargetObject delegate = + new DelegateDbgModel2TargetObject(getModel(), p, key, modelObject, mixins); return delegate; } @@ -226,41 +222,41 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp protected void checkExited(DbgState state, DbgCause cause) { TargetExecutionState exec = TargetExecutionState.INACTIVE; switch (state) { - case NOT_STARTED: { - exec = TargetExecutionState.INACTIVE; - break; - } - case STARTING: { - exec = TargetExecutionState.ALIVE; - break; - } - case RUNNING: { - exec = TargetExecutionState.RUNNING; - resetModified(); - onRunning(); - break; - } - case STOPPED: { - exec = TargetExecutionState.STOPPED; - onStopped(); - break; - } - case EXIT: { - exec = TargetExecutionState.TERMINATED; - onExit(); - break; - } - case SESSION_EXIT: { - getModel().close(); - return; - } + case NOT_STARTED: { + exec = TargetExecutionState.INACTIVE; + break; + } + case STARTING: { + exec = TargetExecutionState.ALIVE; + break; + } + case RUNNING: { + exec = TargetExecutionState.RUNNING; + resetModified(); + onRunning(); + break; + } + case STOPPED: { + exec = TargetExecutionState.STOPPED; + onStopped(); + break; + } + case EXIT: { + exec = TargetExecutionState.TERMINATED; + onExit(); + break; + } + case SESSION_EXIT: { + getModel().close(); + return; + } } if (proxy instanceof TargetExecutionStateful) { if (proxy instanceof DbgModelTargetSession) { if (state != DbgState.EXIT) { setExecutionState(exec, "Refreshed"); } - } + } else { TargetExecutionState previous = this.getExecutionState(); if (!previous.equals(TargetExecutionState.INACTIVE)) { @@ -368,7 +364,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp } if (proxy instanceof TargetAccessConditioned) { changeAttributes(List.of(), List.of(), Map.of( // - TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME, accessible // + TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME, accessible // ), "Accessibility changed"); } } @@ -420,16 +416,18 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp List delegates = new ArrayList<>(); TargetObject stack = (TargetObject) getCachedAttribute("Stack"); if (stack != null) { - DbgModelTargetStack frames = (DbgModelTargetStack) stack.getCachedAttribute("Frames"); + DbgModelTargetStack frames = + (DbgModelTargetStack) stack.getCachedAttribute("Frames"); delegates.add((DelegateDbgModel2TargetObject) frames.getDelegate()); } - DbgModelTargetRegisterContainer container = (DbgModelTargetRegisterContainer) getCachedAttribute( - "Registers"); + DbgModelTargetRegisterContainer container = + (DbgModelTargetRegisterContainer) getCachedAttribute("Registers"); if (container == null) { return; } delegates.add((DelegateDbgModel2TargetObject) container.getDelegate()); - DbgModelTargetRegisterBank bank = (DbgModelTargetRegisterBank) container.getCachedAttribute("User"); + DbgModelTargetRegisterBank bank = + (DbgModelTargetRegisterBank) container.getCachedAttribute("User"); if (bank == null) { return; } @@ -457,7 +455,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp for (TargetObject obj : getCachedElements().values()) { if (obj instanceof TargetStackFrame) { DbgModelTargetObject frame = (DbgModelTargetObject) obj; - DelegateDbgModel2TargetObject delegate = (DelegateDbgModel2TargetObject) frame.getDelegate(); + DelegateDbgModel2TargetObject delegate = + (DelegateDbgModel2TargetObject) frame.getDelegate(); delegate.threadStateChangedSpecific(state, reason); } } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/cmd/AbstractFridaCommand.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/cmd/AbstractFridaCommand.java index a8521dc780..88cbd77924 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/cmd/AbstractFridaCommand.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/cmd/AbstractFridaCommand.java @@ -80,7 +80,7 @@ public abstract class AbstractFridaCommand implements FridaCommand { String type = jobj.get("type").getAsString(); if (type.equals("error")) { String desc = jobj.get("description").getAsString(); - manager.getEventListeners().fire.consoleOutput(desc+"\n", 0); + manager.getEventListeners().invoke().consoleOutput(desc+"\n", 0); Msg.error(this, desc); return; } @@ -88,14 +88,14 @@ public abstract class AbstractFridaCommand implements FridaCommand { if (jobj.has("payload")) { Object object = jobj.get("payload"); if (!(object instanceof JsonPrimitive)) { - manager.getEventListeners().fire.consoleOutput(object+" not a String\n", 0); + manager.getEventListeners().invoke().consoleOutput(object+" not a String\n", 0); Msg.error(this, object); return; } String value = ((JsonPrimitive) object).getAsString(); if (!value.startsWith("{")) { - manager.getEventListeners().fire.consoleOutput(object+"\n", 0); + manager.getEventListeners().invoke().consoleOutput(object+"\n", 0); return; } JsonElement res = JsonParser.parseString(value); @@ -106,14 +106,14 @@ public abstract class AbstractFridaCommand implements FridaCommand { res = keyValue.get("value"); String key = element.getAsString(); if (!key.equals(name)) { - manager.getEventListeners().fire.consoleOutput(res+"\n", 0); + manager.getEventListeners().invoke().consoleOutput(res+"\n", 0); return; } } else { - manager.getEventListeners().fire.consoleOutput(object+"\n", 0); + manager.getEventListeners().invoke().consoleOutput(object+"\n", 0); } } else { - manager.getEventListeners().fire.consoleOutput(object+"\n", 0); + manager.getEventListeners().invoke().consoleOutput(object+"\n", 0); } if ("[]".equals(res.toString())) { Msg.error(this, "nothing returned for "+this); diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/cmd/FridaDetachCommand.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/cmd/FridaDetachCommand.java index d789f6fad0..4ec1e18aae 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/cmd/FridaDetachCommand.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/cmd/FridaDetachCommand.java @@ -46,7 +46,7 @@ public class FridaDetachCommand extends AbstractFridaCommand { for (FridaThread thread : list) { manager.removeThread(pid, FridaClient.getId(thread)); } - manager.getEventListeners().fire.processRemoved(pid, FridaCause.Causes.UNCLAIMED); + manager.getEventListeners().invoke().processRemoved(pid, FridaCause.Causes.UNCLAIMED); return null; } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/impl/FridaManagerImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/impl/FridaManagerImpl.java index 42d4cd526c..072ee6765e 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/impl/FridaManagerImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/manager/impl/FridaManagerImpl.java @@ -70,7 +70,7 @@ public class FridaManagerImpl implements FridaManager { private final HandlerMap, Void, DebugStatus> handlerMap = new HandlerMap<>(); private final Map, DebugStatus> statusMap = new LinkedHashMap<>(); private final ListenerSet listenersEvent = - new ListenerSet<>(FridaEventsListener.class); + new ListenerSet<>(FridaEventsListener.class, true); private FridaTarget currentTarget; private FridaSession currentSession; @@ -157,7 +157,7 @@ public class FridaManagerImpl implements FridaManager { for (String tid : toRemove) { removeThread(processId, tid); } - getEventListeners().fire.processRemoved(id, cause); + getEventListeners().invoke().processRemoved(id, cause); } } @@ -213,7 +213,7 @@ public class FridaManagerImpl implements FridaManager { if (sessions.remove(id) == null) { throw new IllegalArgumentException("There is no session with id " + id); } - getEventListeners().fire.sessionRemoved(id, cause); + getEventListeners().invoke().sessionRemoved(id, cause); } } @@ -672,8 +672,8 @@ public class FridaManagerImpl implements FridaManager { protected DebugStatus processThreadCreated(FridaThreadCreatedEvent evt, Void v) { FridaThread thread = evt.getInfo().thread; currentThread = thread; - getEventListeners().fire.threadCreated(thread, FridaCause.Causes.UNCLAIMED); - getEventListeners().fire.threadSelected(thread, null, evt.getCause()); + getEventListeners().invoke().threadCreated(thread, FridaCause.Causes.UNCLAIMED); + getEventListeners().invoke().threadSelected(thread, null, evt.getCause()); return statusMap.get(evt.getClass()); } @@ -686,8 +686,8 @@ public class FridaManagerImpl implements FridaManager { */ protected DebugStatus processThreadReplaced(FridaThreadReplacedEvent evt, Void v) { FridaThread thread = evt.getInfo().thread; - getEventListeners().fire.threadReplaced(thread, FridaCause.Causes.UNCLAIMED); - getEventListeners().fire.threadSelected(thread, null, evt.getCause()); + getEventListeners().invoke().threadReplaced(thread, FridaCause.Causes.UNCLAIMED); + getEventListeners().invoke().threadSelected(thread, null, evt.getCause()); return statusMap.get(evt.getClass()); } @@ -699,7 +699,7 @@ public class FridaManagerImpl implements FridaManager { * @return retval handling/break status */ protected DebugStatus processThreadExited(FridaThreadExitedEvent evt, Void v) { - getEventListeners().fire.threadExited(currentThread, currentProcess, evt.getCause()); + getEventListeners().invoke().threadExited(currentThread, currentProcess, evt.getCause()); return statusMap.get(evt.getClass()); } @@ -712,7 +712,7 @@ public class FridaManagerImpl implements FridaManager { */ protected DebugStatus processThreadSelected(FridaThreadSelectedEvent evt, Void v) { currentThread = evt.getThread(); - getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause()); + getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause()); return statusMap.get(evt.getClass()); } @@ -725,7 +725,7 @@ public class FridaManagerImpl implements FridaManager { */ protected DebugStatus processFrameSelected(FridaSelectedFrameChangedEvent evt, Void v) { currentThread = evt.getThread(); - getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause()); + getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause()); return statusMap.get(evt.getClass()); } @@ -739,8 +739,8 @@ public class FridaManagerImpl implements FridaManager { protected DebugStatus processProcessCreated(FridaProcessCreatedEvent evt, Void v) { FridaProcessInfo info = evt.getInfo(); FridaProcess proc = info.process; - getEventListeners().fire.processAdded(proc, FridaCause.Causes.UNCLAIMED); - getEventListeners().fire.processSelected(proc, evt.getCause()); + getEventListeners().invoke().processAdded(proc, FridaCause.Causes.UNCLAIMED); + getEventListeners().invoke().processSelected(proc, evt.getCause()); /* FridaThread thread = proc.GetSelectedThread(); @@ -759,8 +759,8 @@ public class FridaManagerImpl implements FridaManager { protected DebugStatus processProcessReplaced(FridaProcessReplacedEvent evt, Void v) { FridaProcessInfo info = evt.getInfo(); FridaProcess proc = info.process; - getEventListeners().fire.processReplaced(proc, FridaCause.Causes.UNCLAIMED); - getEventListeners().fire.processSelected(proc, evt.getCause()); + getEventListeners().invoke().processReplaced(proc, FridaCause.Causes.UNCLAIMED); + getEventListeners().invoke().processSelected(proc, evt.getCause()); /* FridaThread thread = proc.GetSelectedThread(); @@ -779,9 +779,9 @@ public class FridaManagerImpl implements FridaManager { protected DebugStatus processProcessExited(FridaProcessExitedEvent evt, Void v) { FridaThread thread = getCurrentThread(); FridaProcess process = getCurrentProcess(); - getEventListeners().fire.threadExited(thread, process, evt.getCause()); - getEventListeners().fire.processExited(process, evt.getCause()); - getEventListeners().fire.processRemoved(process.getPID().toString(), evt.getCause()); + getEventListeners().invoke().threadExited(thread, process, evt.getCause()); + getEventListeners().invoke().processExited(process, evt.getCause()); + getEventListeners().invoke().processRemoved(process.getPID().toString(), evt.getCause()); return statusMap.get(evt.getClass()); } @@ -794,7 +794,7 @@ public class FridaManagerImpl implements FridaManager { */ protected DebugStatus processProcessSelected(FridaProcessSelectedEvent evt, Void v) { currentProcess = evt.getProcess(); - getEventListeners().fire.processSelected(currentProcess, evt.getCause()); + getEventListeners().invoke().processSelected(currentProcess, evt.getCause()); return statusMap.get(evt.getClass()); } @@ -807,8 +807,8 @@ public class FridaManagerImpl implements FridaManager { */ protected DebugStatus processSessionCreated(FridaSessionCreatedEvent evt, Void v) { FridaSessionInfo info = evt.getInfo(); - getEventListeners().fire.sessionAdded(info.session, FridaCause.Causes.UNCLAIMED); - getEventListeners().fire.sessionSelected(info.session, evt.getCause()); + getEventListeners().invoke().sessionAdded(info.session, FridaCause.Causes.UNCLAIMED); + getEventListeners().invoke().sessionSelected(info.session, evt.getCause()); return statusMap.get(evt.getClass()); } @@ -821,8 +821,8 @@ public class FridaManagerImpl implements FridaManager { */ protected DebugStatus processSessionReplaced(FridaSessionReplacedEvent evt, Void v) { FridaSessionInfo info = evt.getInfo(); - getEventListeners().fire.sessionReplaced(info.session, FridaCause.Causes.UNCLAIMED); - getEventListeners().fire.sessionSelected(info.session, evt.getCause()); + getEventListeners().invoke().sessionReplaced(info.session, FridaCause.Causes.UNCLAIMED); + getEventListeners().invoke().sessionSelected(info.session, evt.getCause()); return statusMap.get(evt.getClass()); } @@ -835,10 +835,10 @@ public class FridaManagerImpl implements FridaManager { */ protected DebugStatus processSessionExited(FridaSessionExitedEvent evt, Void v) { removeSession(evt.sessionId, FridaCause.Causes.UNCLAIMED); - getEventListeners().fire.sessionRemoved(evt.sessionId, evt.getCause()); - getEventListeners().fire.threadExited(currentThread, currentProcess, evt.getCause()); - getEventListeners().fire.processExited(currentProcess, evt.getCause()); - getEventListeners().fire.processRemoved(currentProcess.getPID().toString(), + getEventListeners().invoke().sessionRemoved(evt.sessionId, evt.getCause()); + getEventListeners().invoke().threadExited(currentThread, currentProcess, evt.getCause()); + getEventListeners().invoke().processExited(currentProcess, evt.getCause()); + getEventListeners().invoke().processRemoved(currentProcess.getPID().toString(), evt.getCause()); return statusMap.get(evt.getClass()); } @@ -855,7 +855,7 @@ public class FridaManagerImpl implements FridaManager { long n = info.getNumberOfModules(); FridaProcess process = info.getProcess(); for (int i = 0; i < n; i++) { - getEventListeners().fire.moduleLoaded(process, info, i, evt.getCause()); + getEventListeners().invoke().moduleLoaded(process, info, i, evt.getCause()); } return statusMap.get(evt.getClass()); } @@ -872,7 +872,7 @@ public class FridaManagerImpl implements FridaManager { long n = info.getNumberOfModules(); FridaProcess process = info.getProcess(); for (int i = 0; i < n; i++) { - getEventListeners().fire.moduleReplaced(process, info, i, evt.getCause()); + getEventListeners().invoke().moduleReplaced(process, info, i, evt.getCause()); } return statusMap.get(evt.getClass()); } @@ -889,7 +889,7 @@ public class FridaManagerImpl implements FridaManager { long n = info.getNumberOfModules(); FridaProcess process = info.getProcess(); for (int i = 0; i < n; i++) { - getEventListeners().fire.moduleUnloaded(process, info, i, evt.getCause()); + getEventListeners().invoke().moduleUnloaded(process, info, i, evt.getCause()); } return statusMap.get(evt.getClass()); } @@ -906,7 +906,7 @@ public class FridaManagerImpl implements FridaManager { long n = info.getNumberOfRegions(); FridaProcess process = info.getProcess(); for (int i = 0; i < n; i++) { - getEventListeners().fire.regionAdded(process, info, i, evt.getCause()); + getEventListeners().invoke().regionAdded(process, info, i, evt.getCause()); } return statusMap.get(evt.getClass()); } @@ -923,7 +923,7 @@ public class FridaManagerImpl implements FridaManager { long n = info.getNumberOfRegions(); FridaProcess process = info.getProcess(); for (int i = 0; i < n; i++) { - getEventListeners().fire.regionReplaced(process, info, i, evt.getCause()); + getEventListeners().invoke().regionReplaced(process, info, i, evt.getCause()); } return statusMap.get(evt.getClass()); } @@ -940,7 +940,7 @@ public class FridaManagerImpl implements FridaManager { long n = info.getNumberOfRegions(); FridaProcess process = info.getProcess(); for (int i = 0; i < n; i++) { - getEventListeners().fire.regionRemoved(process, info, i, evt.getCause()); + getEventListeners().invoke().regionRemoved(process, info, i, evt.getCause()); } return statusMap.get(evt.getClass()); } @@ -1000,13 +1000,13 @@ public class FridaManagerImpl implements FridaManager { */ protected DebugStatus processSessionSelected(FridaSessionSelectedEvent evt, Void v) { FridaSession session = evt.getSession(); - getEventListeners().fire.sessionSelected(session, evt.getCause()); + getEventListeners().invoke().sessionSelected(session, evt.getCause()); return statusMap.get(evt.getClass()); } protected void processConsoleOutput(FridaConsoleOutputEvent evt, Void v) { if (evt.getOutput() != null) { - getEventListeners().fire.consoleOutput(evt.getOutput(), evt.getMask()); + getEventListeners().invoke().consoleOutput(evt.getOutput(), evt.getMask()); } } @@ -1195,7 +1195,7 @@ public class FridaManagerImpl implements FridaManager { public CompletableFuture console(String command) { if (continuation != null) { String prompt = command.equals("") ? FridaModelTargetInterpreter.FRIDA_PROMPT : ">>>"; - getEventListeners().fire.promptChanged(prompt); + getEventListeners().invoke().promptChanged(prompt); continuation.complete(command); setContinuation(null); return AsyncUtils.nil(); diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelImpl.java index 769fd803b1..988f88d999 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelImpl.java @@ -98,7 +98,7 @@ public class FridaModelImpl extends AbstractFridaModel implements DebuggerObject @Override public void terminate() throws IOException { - listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); + listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL); root.invalidateSubtree(root, "Frida is terminating"); manager.terminate(); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java index 8078d28f1d..413613b91b 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java @@ -239,11 +239,11 @@ public class GdbManagerImpl implements GdbManager { Collections.unmodifiableMap(breakpoints); protected final ListenerSet listenersEvent = - new ListenerSet<>(GdbEventsListener.class); + new ListenerSet<>(GdbEventsListener.class, true); protected final ListenerSet listenersTargetOutput = - new ListenerSet<>(GdbTargetOutputListener.class); + new ListenerSet<>(GdbTargetOutputListener.class, true); protected final ListenerSet listenersConsoleOutput = - new ListenerSet<>(GdbConsoleOutputListener.class); + new ListenerSet<>(GdbConsoleOutputListener.class, true); protected final ExecutorService eventThread = Executors.newSingleThreadExecutor(); /** @@ -401,7 +401,7 @@ public class GdbManagerImpl implements GdbManager { @Internal // for detach command public void fireThreadExited(int tid, GdbInferiorImpl inferior, GdbCause cause) { - event(() -> listenersEvent.fire.threadExited(tid, inferior, cause), "threadExited"); + event(() -> listenersEvent.invoke().threadExited(tid, inferior, cause), "threadExited"); } @Override @@ -474,7 +474,7 @@ public class GdbManagerImpl implements GdbManager { throw new IllegalArgumentException("There is already inferior " + exists); } inferiors.put(inferior.getId(), inferior); - event(() -> listenersEvent.fire.inferiorAdded(inferior, cause), "addInferior"); + event(() -> listenersEvent.invoke().inferiorAdded(inferior, cause), "addInferior"); } /** @@ -488,7 +488,7 @@ public class GdbManagerImpl implements GdbManager { if (inferiors.remove(iid) == null) { throw new IllegalArgumentException("There is no inferior with id " + iid); } - event(() -> listenersEvent.fire.inferiorRemoved(iid, cause), "removeInferior"); + event(() -> listenersEvent.invoke().inferiorRemoved(iid, cause), "removeInferior"); } /** @@ -505,7 +505,7 @@ public class GdbManagerImpl implements GdbManager { if (curInferior != inf) { curInferior = inf; if (fire) { - event(() -> listenersEvent.fire.inferiorSelected(inf, cause), + event(() -> listenersEvent.invoke().inferiorSelected(inf, cause), "updateCurrentInferior"); } return true; @@ -1080,7 +1080,7 @@ public class GdbManagerImpl implements GdbManager { String out = evt.getOutput(); //System.out.print(out); if (!evt.isStolen()) { - listenersConsoleOutput.fire.output(Channel.STDOUT, out); + listenersConsoleOutput.invoke().output(Channel.STDOUT, out); } if (evt.getInterpreter() == Interpreter.MI2 && out.toLowerCase().contains("switching to inferior")) { @@ -1097,7 +1097,7 @@ public class GdbManagerImpl implements GdbManager { * @param v nothing */ protected void processTargetOut(GdbTargetOutputEvent evt, Void v) { - listenersTargetOutput.fire.output(evt.getOutput()); + listenersTargetOutput.invoke().output(evt.getOutput()); } /** @@ -1110,7 +1110,7 @@ public class GdbManagerImpl implements GdbManager { String out = evt.getOutput(); //System.err.print(out); if (!evt.isStolen()) { - listenersConsoleOutput.fire.output(Channel.STDERR, out); + listenersConsoleOutput.invoke().output(Channel.STDERR, out); } } @@ -1134,7 +1134,7 @@ public class GdbManagerImpl implements GdbManager { } inferior.add(evt.getCause()); if (fireSelected) { - event(() -> listenersEvent.fire.inferiorSelected(inferior, evt.getCause()), + event(() -> listenersEvent.invoke().inferiorSelected(inferior, evt.getCause()), "groupAdded-sel"); } } @@ -1161,7 +1161,7 @@ public class GdbManagerImpl implements GdbManager { } inferior.remove(evt.getCause()); if (fireSelected) { - event(() -> listenersEvent.fire.inferiorSelected(cur, evt.getCause()), + event(() -> listenersEvent.invoke().inferiorSelected(cur, evt.getCause()), "groupRemoved-sel"); // Also cause GDB to generate thread selection events, if applicable setActiveInferior(cur, false); @@ -1182,7 +1182,7 @@ public class GdbManagerImpl implements GdbManager { } public void fireInferiorStarted(GdbInferiorImpl inf, GdbCause cause, String text) { - event(() -> listenersEvent.fire.inferiorStarted(inf, cause), text); + event(() -> listenersEvent.invoke().inferiorStarted(inf, cause), text); } /** @@ -1195,7 +1195,7 @@ public class GdbManagerImpl implements GdbManager { int iid = evt.getInferiorId(); GdbInferiorImpl inf = getInferior(iid); inf.setExitCode(evt.getExitCode()); - event(() -> listenersEvent.fire.inferiorExited(inf, evt.getCause()), "inferiorExited"); + event(() -> listenersEvent.invoke().inferiorExited(inf, evt.getCause()), "inferiorExited"); } /** @@ -1210,7 +1210,7 @@ public class GdbManagerImpl implements GdbManager { GdbInferiorImpl inf = getInferior(iid); GdbThreadImpl thread = new GdbThreadImpl(this, inf, tid); thread.add(); - event(() -> listenersEvent.fire.threadCreated(thread, evt.getCause()), "threadCreated"); + event(() -> listenersEvent.invoke().threadCreated(thread, evt.getCause()), "threadCreated"); } /** @@ -1225,7 +1225,7 @@ public class GdbManagerImpl implements GdbManager { GdbInferiorImpl inf = getInferior(iid); GdbThreadImpl thread = inf.getThread(tid); thread.remove(); - event(() -> listenersEvent.fire.threadExited(tid, inf, evt.getCause()), "threadExited"); + event(() -> listenersEvent.invoke().threadExited(tid, inf, evt.getCause()), "threadExited"); } /** @@ -1250,7 +1250,7 @@ public class GdbManagerImpl implements GdbManager { */ public void doThreadSelected(GdbThreadImpl thread, GdbStackFrame frame, GdbCause cause) { updateCurrentInferior(thread.getInferior(), cause, true); - event(() -> listenersEvent.fire.threadSelected(thread, frame, cause), "threadSelected"); + event(() -> listenersEvent.invoke().threadSelected(thread, frame, cause), "threadSelected"); } /** @@ -1265,14 +1265,14 @@ public class GdbManagerImpl implements GdbManager { if (iid == null) { // Context of all inferiors for (GdbInferiorImpl inf : inferiors.values()) { inf.libraryLoaded(name); - event(() -> listenersEvent.fire.libraryLoaded(inf, name, evt.getCause()), + event(() -> listenersEvent.invoke().libraryLoaded(inf, name, evt.getCause()), "libraryLoaded"); } } else { GdbInferiorImpl inf = getInferior(iid); inf.libraryLoaded(name); - event(() -> listenersEvent.fire.libraryLoaded(inf, name, evt.getCause()), + event(() -> listenersEvent.invoke().libraryLoaded(inf, name, evt.getCause()), "libraryLoaded"); } } @@ -1289,14 +1289,14 @@ public class GdbManagerImpl implements GdbManager { if (iid == null) { // Context of all inferiors for (GdbInferiorImpl inf : inferiors.values()) { inf.libraryUnloaded(name); - event(() -> listenersEvent.fire.libraryUnloaded(inf, name, evt.getCause()), + event(() -> listenersEvent.invoke().libraryUnloaded(inf, name, evt.getCause()), "libraryUnloaded"); } } else { GdbInferiorImpl inf = getInferior(iid); inf.libraryUnloaded(name); - event(() -> listenersEvent.fire.libraryUnloaded(inf, name, evt.getCause()), + event(() -> listenersEvent.invoke().libraryUnloaded(inf, name, evt.getCause()), "libraryUnloaded"); } } @@ -1310,7 +1310,7 @@ public class GdbManagerImpl implements GdbManager { @Internal public void doBreakpointCreated(GdbBreakpointInfo newInfo, GdbCause cause) { addKnownBreakpoint(newInfo, false); - event(() -> listenersEvent.fire.breakpointCreated(newInfo, cause), "breakpointCreated"); + event(() -> listenersEvent.invoke().breakpointCreated(newInfo, cause), "breakpointCreated"); } /** @@ -1332,7 +1332,7 @@ public class GdbManagerImpl implements GdbManager { @Internal public void doBreakpointModified(GdbBreakpointInfo newInfo, GdbCause cause) { GdbBreakpointInfo oldInfo = addKnownBreakpoint(newInfo, true); - event(() -> listenersEvent.fire.breakpointModified(newInfo, oldInfo, cause), + event(() -> listenersEvent.invoke().breakpointModified(newInfo, oldInfo, cause), "breakpointModified"); } @@ -1358,7 +1358,7 @@ public class GdbManagerImpl implements GdbManager { if (oldInfo == null) { return; } - event(() -> listenersEvent.fire.breakpointDeleted(oldInfo, cause), "breakpointDeleted"); + event(() -> listenersEvent.invoke().breakpointDeleted(oldInfo, cause), "breakpointDeleted"); } protected void doBreakpointModifiedSameLocations(GdbBreakpointInfo newInfo, @@ -1367,7 +1367,7 @@ public class GdbManagerImpl implements GdbManager { return; } addKnownBreakpoint(newInfo, true); - event(() -> listenersEvent.fire.breakpointModified(newInfo, oldInfo, cause), + event(() -> listenersEvent.invoke().breakpointModified(newInfo, oldInfo, cause), "breakpointModified"); } @@ -1412,7 +1412,7 @@ public class GdbManagerImpl implements GdbManager { protected void processMemoryChanged(GdbMemoryChangedEvent evt, Void v) { int iid = evt.getInferiorId(); GdbInferior inf = getInferior(iid); - event(() -> listenersEvent.fire.memoryChanged(inf, evt.getAddress(), evt.getLength(), + event(() -> listenersEvent.invoke().memoryChanged(inf, evt.getAddress(), evt.getLength(), evt.getCause()), "memoryChanged"); } @@ -1423,7 +1423,7 @@ public class GdbManagerImpl implements GdbManager { * @param v nothing */ protected void processParamChanged(GdbParamChangedEvent evt, Void v) { - event(() -> listenersEvent.fire.paramChanged(evt.getParam(), evt.getValue(), + event(() -> listenersEvent.invoke().paramChanged(evt.getParam(), evt.getValue(), evt.getCause()), "paramChanged"); } @@ -1467,7 +1467,7 @@ public class GdbManagerImpl implements GdbManager { GdbMiFieldList newFrame = evt.checkFrame(); GdbStackFrameImpl frame = newFrame == null ? null : GdbStackFrameImpl.fromFieldList(thread, newFrame); - event(() -> listenersEvent.fire.threadSelected(thread, frame, evt), "command-done"); + event(() -> listenersEvent.invoke().threadSelected(thread, frame, evt), "command-done"); } /** @@ -1538,7 +1538,7 @@ public class GdbManagerImpl implements GdbManager { if ("all".equals(threadId)) { GdbInferiorImpl cur = curInferior; event(() -> { - listenersEvent.fire.inferiorStateChanged(cur, cur.getKnownThreads().values(), + listenersEvent.invoke().inferiorStateChanged(cur, cur.getKnownThreads().values(), evt.newState(), null, evt.getCause(), evt.getReason()); }, "inferiorState-running"); for (GdbThreadImpl thread : curInferior.getKnownThreadsImpl().values()) { @@ -1549,7 +1549,7 @@ public class GdbManagerImpl implements GdbManager { int id = Integer.parseUnsignedInt(threadId); GdbThreadImpl thread = threads.get(id); event(() -> { - listenersEvent.fire.inferiorStateChanged(thread.getInferior(), + listenersEvent.invoke().inferiorStateChanged(thread.getInferior(), List.of(thread), evt.newState(), null, evt.getCause(), evt.getReason()); }, "inferiorState-running"); thread.setState(evt.newState(), evt.getCause(), evt.getReason()); @@ -1584,13 +1584,13 @@ public class GdbManagerImpl implements GdbManager { } for (Map.Entry> ent : byInf.entrySet()) { event(() -> { - listenersEvent.fire.inferiorStateChanged(ent.getKey(), ent.getValue(), + listenersEvent.invoke().inferiorStateChanged(ent.getKey(), ent.getValue(), evt.newState(), evtThread, evt.getCause(), evt.getReason()); }, "inferiorState-stopped"); } if (evtThread != null) { GdbStackFrameImpl frame = evt.getFrame(evtThread); - event(() -> listenersEvent.fire.threadSelected(evtThread, frame, evt), + event(() -> listenersEvent.invoke().threadSelected(evtThread, frame, evt), "inferiorState-stopped"); } } @@ -1701,7 +1701,7 @@ public class GdbManagerImpl implements GdbManager { @Internal public void synthesizeConsoleOut(Channel channel, String line) { - listenersConsoleOutput.fire.output(channel, line); + listenersConsoleOutput.invoke().output(channel, line); } @Override diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbThreadImpl.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbThreadImpl.java index 56b58675d2..ed7d35d50d 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbThreadImpl.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbThreadImpl.java @@ -81,7 +81,7 @@ public class GdbThreadImpl implements GdbThread { this.inferior.addThread(this); this.manager.addThread(this); state.addChangeListener((oldState, newState, pair) -> { - manager.event(() -> manager.listenersEvent.fire.threadStateChanged(this, newState, + manager.event(() -> manager.listenersEvent.invoke().threadStateChanged(this, newState, pair.cause, pair.reason), "threadState"); }); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java index a792c5338a..c94eb64299 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java @@ -167,7 +167,7 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel { } public void terminate() throws IOException { - listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); + listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL); session.invalidateSubtree(session, "GDB is terminating"); gdb.terminate(); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointSpec.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointSpec.java index 79768f5d39..8aba8a6e35 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointSpec.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetBreakpointSpec.java @@ -15,7 +15,8 @@ */ package agent.gdb.model.impl; -import java.util.*; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -30,15 +31,11 @@ import ghidra.dbg.target.schema.TargetAttributeType; import ghidra.dbg.target.schema.TargetObjectSchemaInfo; import ghidra.dbg.util.PathUtils; import ghidra.util.Msg; -import ghidra.util.datastruct.ListenerMap.ListenerEntry; import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.WeakValueHashMap; -@TargetObjectSchemaInfo( - name = "BreakpointSpec", - attributes = { - @TargetAttributeType(type = Void.class) }, - canonicalContainer = true) +@TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = { + @TargetAttributeType(type = Void.class) }, canonicalContainer = true) public class GdbModelTargetBreakpointSpec extends DefaultTargetObject implements TargetBreakpointSpec, TargetDeletable { @@ -62,12 +59,7 @@ public class GdbModelTargetBreakpointSpec extends protected final Map breaksBySub = new WeakValueHashMap<>(); protected final ListenerSet actions = - new ListenerSet<>(TargetBreakpointAction.class) { - // Use strong references on actions - protected Map> createMap() { - return new LinkedHashMap<>(); - }; - }; + new ListenerSet<>(TargetBreakpointAction.class, false); public GdbModelTargetBreakpointSpec(GdbModelTargetBreakpointContainer breakpoints, GdbBreakpointInfo info) { @@ -171,13 +163,12 @@ public class GdbModelTargetBreakpointSpec extends } protected void updateAttributesFromInfo(String reason) { - changeAttributes(List.of(), Map.of( - ENABLED_ATTRIBUTE_NAME, enabled = info.isEnabled(), - EXPRESSION_ATTRIBUTE_NAME, - expression = info.getType() == GdbBreakpointType.CATCHPOINT ? info.getCatchType() - : info.getOriginalLocation(), - KINDS_ATTRIBUTE_NAME, kinds = computeKinds(info), - DISPLAY_ATTRIBUTE_NAME, display = computeDisplay()), + changeAttributes(List.of(), + Map.of(ENABLED_ATTRIBUTE_NAME, enabled = info.isEnabled(), EXPRESSION_ATTRIBUTE_NAME, + expression = info.getType() == GdbBreakpointType.CATCHPOINT ? info.getCatchType() + : info.getOriginalLocation(), + KINDS_ATTRIBUTE_NAME, kinds = computeKinds(info), DISPLAY_ATTRIBUTE_NAME, + display = computeDisplay()), reason); } @@ -232,7 +223,7 @@ public class GdbModelTargetBreakpointSpec extends protected void breakpointHit(GdbModelTargetStackFrame frame, GdbModelTargetBreakpointLocation eb) { - actions.fire.breakpointHit(this, frame.thread, frame, eb); + actions.invoke().breakpointHit(this, frame.thread, frame, eb); } public synchronized GdbModelTargetBreakpointLocation getTargetBreakpointLocation( @@ -270,15 +261,21 @@ public class GdbModelTargetBreakpointSpec extends case BREAKPOINT: case HW_BREAKPOINT: case OTHER: - return String.format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(), - info.getDisp(), enb, addr, what).trim(); + return String + .format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(), + info.getDisp(), enb, addr, what) + .trim(); case CATCHPOINT: - return String.format("%d %s %s %s %s", info.getNumber(), info.getTypeName(), - info.getDisp(), enb, what).trim(); + return String + .format("%d %s %s %s %s", info.getNumber(), info.getTypeName(), + info.getDisp(), enb, what) + .trim(); case DPRINTF: // TODO: script? - return String.format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(), - info.getDisp(), enb, addr, what).trim(); + return String + .format("%d %s %s %s %s %s", info.getNumber(), info.getTypeName(), + info.getDisp(), enb, addr, what) + .trim(); } throw new AssertionError(); } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/cmd/LldbDetachCommand.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/cmd/LldbDetachCommand.java index ae40167591..2975422896 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/cmd/LldbDetachCommand.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/cmd/LldbDetachCommand.java @@ -39,7 +39,7 @@ public class LldbDetachCommand extends AbstractLldbCommand { SBThread t = process.GetThreadAtIndex(i); manager.removeThread(pid, DebugClient.getId(t)); } - manager.getEventListeners().fire.processRemoved(pid, LldbCause.Causes.UNCLAIMED); + manager.getEventListeners().invoke().processRemoved(pid, LldbCause.Causes.UNCLAIMED); return null; } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/impl/LldbManagerImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/impl/LldbManagerImpl.java index 9461bb5d56..0b07a4649e 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/impl/LldbManagerImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/manager/impl/LldbManagerImpl.java @@ -94,7 +94,7 @@ public class LldbManagerImpl implements LldbManager { private final HandlerMap, Void, DebugStatus> handlerMap = new HandlerMap<>(); private final Map, DebugStatus> statusMap = new LinkedHashMap<>(); private final ListenerSet listenersEvent = - new ListenerSet<>(LldbEventsListener.class); + new ListenerSet<>(LldbEventsListener.class, true); private SBEvent currentEvent; private SBTarget currentSession; @@ -116,7 +116,7 @@ public class LldbManagerImpl implements LldbManager { /** * Use {@link SBThreadImpl#remove()} instead - * + * * @param id the thread ID to remove */ public void removeThread(String processId, String id) { @@ -136,8 +136,9 @@ public class LldbManagerImpl implements LldbManager { public void addThreadIfAbsent(SBProcess process, SBThread thread) { synchronized (threads) { - if (!process.IsValid()) + if (!process.IsValid()) { return; + } Map map = threads.get(DebugClient.getId(process)); if (map == null) { map = new HashMap<>(); @@ -165,7 +166,7 @@ public class LldbManagerImpl implements LldbManager { /** * Use {@link SBProcessImpl#remove(LldbCause)} instead - * + * * @param id the process ID to remove * @param cause the cause of removal */ @@ -188,18 +189,10 @@ public class LldbManagerImpl implements LldbManager { for (String tid : toRemove) { removeThread(processId, tid); } - getEventListeners().fire.processRemoved(id, cause); + getEventListeners().invoke().processRemoved(id, cause); } } - /** - * Update the selected process - * - * @param process the process that now has focus - * @param cause the cause of the focus change - * @param fire signal listeners - * @return success status - */ @Override public SBProcess getProcess(SBTarget session, String id) { synchronized (processes) { @@ -214,8 +207,9 @@ public class LldbManagerImpl implements LldbManager { public void addProcessIfAbsent(SBTarget session, SBProcess process) { synchronized (processes) { - if (!session.IsValid()) + if (!session.IsValid()) { return; + } String sessionId = DebugClient.getId(session); Map map = processes.get(sessionId); if (map == null) { @@ -242,7 +236,7 @@ public class LldbManagerImpl implements LldbManager { /** * Use {@link SBTargetImpl#remove(LldbCause)} instead - * + * * @param id the session ID to remove * @param cause the cause of removal */ @@ -252,7 +246,7 @@ public class LldbManagerImpl implements LldbManager { if (sessions.remove(id) == null) { throw new IllegalArgumentException("There is no session with id " + id); } - getEventListeners().fire.sessionRemoved(id, cause); + getEventListeners().invoke().sessionRemoved(id, cause); } } @@ -290,7 +284,7 @@ public class LldbManagerImpl implements LldbManager { /** * Use {@link SBModule#remove()} instead - * + * * @param id the module name to remove */ public void removeModule(SBTarget session, String id) { @@ -310,8 +304,9 @@ public class LldbManagerImpl implements LldbManager { public void addModuleIfAbsent(SBTarget session, SBModule module) { synchronized (modules) { - if (!session.IsValid()) + if (!session.IsValid()) { return; + } String sessionId = DebugClient.getId(session); Map map = modules.get(sessionId); if (map == null) { @@ -347,8 +342,9 @@ public class LldbManagerImpl implements LldbManager { public void addBreakpointIfAbsent(SBTarget session, Object bpt) { synchronized (breakpoints) { - if (!session.IsValid()) + if (!session.IsValid()) { return; + } String sessionId = DebugClient.getId(session); Map map = breakpoints.get(sessionId); if (map == null) { @@ -484,13 +480,11 @@ public class LldbManagerImpl implements LldbManager { state.set(null, Causes.UNCLAIMED); boolean create = true; if (args.length == 0) { - executor = - new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient()); + executor = new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient()); } else { // TODO - process args - executor = - new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient()); + executor = new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient()); create = false; } executor.setManager(this); @@ -540,7 +534,7 @@ public class LldbManagerImpl implements LldbManager { /** * Schedule a command for execution - * + * * @param cmd the command to execute * @return the pending command, which acts as a future for later completion */ @@ -741,16 +735,14 @@ public class LldbManagerImpl implements LldbManager { @Override public void updateState(SBProcess process) { currentProcess = eventProcess = process; - if (currentSession == null || - !currentSession.IsValid() || + if (currentSession == null || !currentSession.IsValid() || !currentSession.equals(process.GetTarget())) { SBTarget candidateSession = currentProcess.GetTarget(); if (candidateSession != null && candidateSession.IsValid()) { currentSession = eventSession = candidateSession; } } - if (currentThread == null || - !currentThread.IsValid() || + if (currentThread == null || !currentThread.IsValid() || !currentThread.equals(process.GetSelectedThread())) { SBThread candidateThread = currentProcess.GetSelectedThread(); if (candidateThread != null && candidateThread.IsValid()) { @@ -764,7 +756,7 @@ public class LldbManagerImpl implements LldbManager { /** * Default handler for events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -775,7 +767,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -785,7 +777,7 @@ public class LldbManagerImpl implements LldbManager { for (int i = 0; i < currentSession.GetNumBreakpoints(); i++) { SBBreakpoint bpt = currentSession.GetBreakpointAtIndex(i); if (bpt.IsValid() && (bpt.GetID() == id.intValue())) { - getEventListeners().fire.breakpointHit(bpt, evt.getCause()); + getEventListeners().invoke().breakpointHit(bpt, evt.getCause()); } } return statusMap.get(evt.getClass()); @@ -793,7 +785,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -801,7 +793,7 @@ public class LldbManagerImpl implements LldbManager { protected DebugStatus processException(LldbExceptionEvent evt, Void v) { /* Integer eventId = updateState(evt); - + DebugExceptionRecord64 info = evt.getInfo(); String key = Integer.toHexString(info.code); if (statusByNameMap.containsKey(key)) { @@ -813,7 +805,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -824,72 +816,72 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for thread created events - * + * * @param evt the event * @param v nothing * @return retval handling/break status */ protected DebugStatus processThreadCreated(LldbThreadCreatedEvent evt, Void v) { SBThread thread = evt.getInfo().thread; - getEventListeners().fire.threadCreated(thread, LldbCause.Causes.UNCLAIMED); - getEventListeners().fire.threadSelected(thread, null, evt.getCause()); + getEventListeners().invoke().threadCreated(thread, LldbCause.Causes.UNCLAIMED); + getEventListeners().invoke().threadSelected(thread, null, evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for thread created events - * + * * @param evt the event * @param v nothing * @return retval handling/break status */ protected DebugStatus processThreadReplaced(LldbThreadReplacedEvent evt, Void v) { SBThread thread = evt.getInfo().thread; - getEventListeners().fire.threadSelected(thread, null, evt.getCause()); + getEventListeners().invoke().threadSelected(thread, null, evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for thread exited events - * + * * @param evt the event * @param v nothing * @return retval handling/break status */ protected DebugStatus processThreadExited(LldbThreadExitedEvent evt, Void v) { - getEventListeners().fire.threadExited(eventThread, eventProcess, evt.getCause()); + getEventListeners().invoke().threadExited(eventThread, eventProcess, evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for thread selected events - * + * * @param evt the event * @param v nothing * @return retval handling/break status */ protected DebugStatus processThreadSelected(LldbThreadSelectedEvent evt, Void v) { currentThread = evt.getThread(); - getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause()); + getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for frame selected events - * + * * @param evt the event * @param v nothing * @return retval handling/break status */ protected DebugStatus processFrameSelected(LldbSelectedFrameChangedEvent evt, Void v) { currentThread = evt.getThread(); - getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause()); + getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for process created events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -897,17 +889,17 @@ public class LldbManagerImpl implements LldbManager { protected DebugStatus processProcessCreated(LldbProcessCreatedEvent evt, Void v) { DebugProcessInfo info = evt.getInfo(); SBProcess proc = info.process; - getEventListeners().fire.processAdded(proc, LldbCause.Causes.UNCLAIMED); - getEventListeners().fire.processSelected(proc, evt.getCause()); + getEventListeners().invoke().processAdded(proc, LldbCause.Causes.UNCLAIMED); + getEventListeners().invoke().processSelected(proc, evt.getCause()); SBThread thread = proc.GetSelectedThread(); - getEventListeners().fire.threadSelected(thread, null, evt.getCause()); + getEventListeners().invoke().threadSelected(thread, null, evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for process replaced events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -915,17 +907,17 @@ public class LldbManagerImpl implements LldbManager { protected DebugStatus processProcessReplaced(LldbProcessReplacedEvent evt, Void v) { DebugProcessInfo info = evt.getInfo(); SBProcess proc = info.process; - getEventListeners().fire.processReplaced(proc, LldbCause.Causes.UNCLAIMED); - getEventListeners().fire.processSelected(proc, evt.getCause()); + getEventListeners().invoke().processReplaced(proc, LldbCause.Causes.UNCLAIMED); + getEventListeners().invoke().processSelected(proc, evt.getCause()); SBThread thread = proc.GetSelectedThread(); - getEventListeners().fire.threadSelected(thread, null, evt.getCause()); + getEventListeners().invoke().threadSelected(thread, null, evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for process exited events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -933,73 +925,74 @@ public class LldbManagerImpl implements LldbManager { protected DebugStatus processProcessExited(LldbProcessExitedEvent evt, Void v) { SBThread thread = getCurrentThread(); SBProcess process = getCurrentProcess(); - getEventListeners().fire.threadExited(thread, process, evt.getCause()); - getEventListeners().fire.processExited(process, evt.getCause()); - getEventListeners().fire.processRemoved(process.GetProcessID().toString(), evt.getCause()); + getEventListeners().invoke().threadExited(thread, process, evt.getCause()); + getEventListeners().invoke().processExited(process, evt.getCause()); + getEventListeners().invoke() + .processRemoved(process.GetProcessID().toString(), evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for process selected events - * + * * @param evt the event * @param v nothing * @return retval handling/break status */ protected DebugStatus processProcessSelected(LldbProcessSelectedEvent evt, Void v) { currentProcess = evt.getProcess(); - getEventListeners().fire.processSelected(currentProcess, evt.getCause()); + getEventListeners().invoke().processSelected(currentProcess, evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for session created events - * + * * @param evt the event * @param v nothing * @return retval handling/break status */ protected DebugStatus processSessionCreated(LldbSessionCreatedEvent evt, Void v) { DebugSessionInfo info = evt.getInfo(); - getEventListeners().fire.sessionAdded(info.session, LldbCause.Causes.UNCLAIMED); - getEventListeners().fire.sessionSelected(info.session, evt.getCause()); + getEventListeners().invoke().sessionAdded(info.session, LldbCause.Causes.UNCLAIMED); + getEventListeners().invoke().sessionSelected(info.session, evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for session replaced events - * + * * @param evt the event * @param v nothing * @return retval handling/break status */ protected DebugStatus processSessionReplaced(LldbSessionReplacedEvent evt, Void v) { DebugSessionInfo info = evt.getInfo(); - getEventListeners().fire.sessionReplaced(info.session, LldbCause.Causes.UNCLAIMED); - getEventListeners().fire.sessionSelected(info.session, evt.getCause()); + getEventListeners().invoke().sessionReplaced(info.session, LldbCause.Causes.UNCLAIMED); + getEventListeners().invoke().sessionSelected(info.session, evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for session exited events - * + * * @param evt the event * @param v nothing * @return retval handling/break status */ protected DebugStatus processSessionExited(LldbSessionExitedEvent evt, Void v) { removeSession(evt.sessionId, LldbCause.Causes.UNCLAIMED); - getEventListeners().fire.sessionRemoved(evt.sessionId, evt.getCause()); - getEventListeners().fire.threadExited(eventThread, eventProcess, evt.getCause()); - getEventListeners().fire.processExited(eventProcess, evt.getCause()); - getEventListeners().fire.processRemoved(eventProcess.GetProcessID().toString(), - evt.getCause()); + getEventListeners().invoke().sessionRemoved(evt.sessionId, evt.getCause()); + getEventListeners().invoke().threadExited(eventThread, eventProcess, evt.getCause()); + getEventListeners().invoke().processExited(eventProcess, evt.getCause()); + getEventListeners().invoke() + .processRemoved(eventProcess.GetProcessID().toString(), evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for module loaded events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -1009,14 +1002,14 @@ public class LldbManagerImpl implements LldbManager { long n = info.getNumberOfModules(); SBProcess process = info.getProcess(); for (int i = 0; i < n; i++) { - getEventListeners().fire.moduleLoaded(process, info, i, evt.getCause()); + getEventListeners().invoke().moduleLoaded(process, info, i, evt.getCause()); } return statusMap.get(evt.getClass()); } /** * Handler for module unloaded events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -1026,14 +1019,14 @@ public class LldbManagerImpl implements LldbManager { long n = info.getNumberOfModules(); SBProcess process = info.getProcess(); for (int i = 0; i < n; i++) { - getEventListeners().fire.moduleUnloaded(process, info, i, evt.getCause()); + getEventListeners().invoke().moduleUnloaded(process, info, i, evt.getCause()); } return statusMap.get(evt.getClass()); } /** * Handler for state changed events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -1092,20 +1085,20 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for session selected events - * + * * @param evt the event * @param v nothing * @return retval handling/break status */ protected DebugStatus processSessionSelected(LldbSessionSelectedEvent evt, Void v) { SBTarget session = evt.getSession(); - getEventListeners().fire.sessionSelected(session, evt.getCause()); + getEventListeners().invoke().sessionSelected(session, evt.getCause()); return statusMap.get(evt.getClass()); } /** * Handler for systems events - * + * * @param evt the event * @param v nothing * @return retval handling/break status @@ -1116,13 +1109,13 @@ public class LldbManagerImpl implements LldbManager { protected void processConsoleOutput(LldbConsoleOutputEvent evt, Void v) { if (evt.getOutput() != null) { - getEventListeners().fire.consoleOutput(evt.getOutput(), evt.getMask()); + getEventListeners().invoke().consoleOutput(evt.getOutput(), evt.getMask()); } } /** * Handler for breakpoint-created event - * + * * @param evt the event * @param v nothing */ @@ -1134,7 +1127,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-modified event - * + * * @param evt the event * @param v nothing */ @@ -1146,7 +1139,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-deleted event - * + * * @param evt the event * @param v nothing */ @@ -1157,7 +1150,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-enable event - * + * * @param evt the event * @param v nothing */ @@ -1168,7 +1161,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-deleted event - * + * * @param evt the event * @param v nothing */ @@ -1179,7 +1172,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-invalidated event - * + * * @param evt the event * @param v nothing */ @@ -1191,7 +1184,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-modified event - * + * * @param evt the event * @param v nothing */ @@ -1204,12 +1197,11 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-locations added event - * + * * @param evt the event * @param v nothing */ - protected void processBreakpointLocationsAdded(LldbBreakpointLocationsAddedEvent evt, - Void v) { + protected void processBreakpointLocationsAdded(LldbBreakpointLocationsAddedEvent evt, Void v) { SBTarget session = getCurrentSession(); Object info = evt.getBreakpointInfo(); doBreakpointModified(session, info, evt.getCause()); @@ -1217,7 +1209,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-locations removed event - * + * * @param evt the event * @param v nothing */ @@ -1230,7 +1222,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-modified event - * + * * @param evt the event * @param v nothing */ @@ -1243,7 +1235,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-modified event - * + * * @param evt the event * @param v nothing */ @@ -1255,7 +1247,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-modified event - * + * * @param evt the event * @param v nothing */ @@ -1268,7 +1260,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-modified event - * + * * @param evt the event * @param v nothing */ @@ -1280,7 +1272,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-modified event - * + * * @param evt the event * @param v nothing */ @@ -1292,7 +1284,7 @@ public class LldbManagerImpl implements LldbManager { /** * Handler for breakpoint-modified event - * + * * @param evt the event * @param v nothing */ @@ -1304,31 +1296,31 @@ public class LldbManagerImpl implements LldbManager { /** * Fire breakpoint created event - * + * * @param newInfo the new information * @param cause the cause of the creation */ @Internal public void doBreakpointCreated(SBTarget session, Object info, LldbCause cause) { addKnownBreakpoint(session, info, false); - getEventListeners().fire.breakpointCreated(info, cause); + getEventListeners().invoke().breakpointCreated(info, cause); } /** * Fire breakpoint modified event - * + * * @param newInfo the new information * @param cause the cause of the modification */ @Internal public void doBreakpointModified(SBTarget session, Object info, LldbCause cause) { addKnownBreakpoint(session, info, true); - getEventListeners().fire.breakpointModified(info, cause); + getEventListeners().invoke().breakpointModified(info, cause); } /** * Fire breakpoint deleted event - * + * * @param number the deleted breakpoint number * @param cause the cause of the deletion */ @@ -1338,13 +1330,13 @@ public class LldbManagerImpl implements LldbManager { if (oldInfo == null) { return; } - getEventListeners().fire.breakpointDeleted(oldInfo, cause); + getEventListeners().invoke().breakpointDeleted(oldInfo, cause); } protected void doBreakpointModifiedSameLocations(SBTarget session, Object info, LldbCause cause) { addKnownBreakpoint(session, info, true); - getEventListeners().fire.breakpointModified(info, cause); + getEventListeners().invoke().breakpointModified(info, cause); } @Internal @@ -1568,16 +1560,16 @@ public class LldbManagerImpl implements LldbManager { Msg.warn(this, "defaulting to active thread"); return currentThread; } - + for (int i = 0; i < currentProcess.GetNumThreads(); i++) { SBThread thread = currentProcess.GetThreadAtIndex(i); if (thread.IsValid()) { - Msg.warn(this, "defaulting to thread "+i); + Msg.warn(this, "defaulting to thread " + i); currentThread = thread; break; } } - + return currentThread; } @@ -1638,7 +1630,7 @@ public class LldbManagerImpl implements LldbManager { public CompletableFuture console(String command) { if (continuation != null) { String prompt = command.equals("") ? LldbModelTargetInterpreter.LLDB_PROMPT : ">>>"; - getEventListeners().fire.promptChanged(prompt); + getEventListeners().invoke().promptChanged(prompt); continuation.complete(command); setContinuation(null); return AsyncUtils.nil(); diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface2/LldbModelTargetBreakpointSpec.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface2/LldbModelTargetBreakpointSpec.java index 1cbb1f5755..51c5fee986 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface2/LldbModelTargetBreakpointSpec.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/iface2/LldbModelTargetBreakpointSpec.java @@ -88,7 +88,7 @@ public interface LldbModelTargetBreakpointSpec extends // public default void breakpointHit() { LldbModelTargetThread targetThread = getParentProcess().getThreads().getTargetThread(getManager().getEventThread()); - getActions().fire.breakpointHit((LldbModelTargetBreakpointSpec) getProxy(), targetThread, + getActions().invoke().breakpointHit((LldbModelTargetBreakpointSpec) getProxy(), targetThread, null, findLocation(targetThread)); } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelImpl.java index 0845a6e2a8..4f9120a1e5 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelImpl.java @@ -110,7 +110,7 @@ public class LldbModelImpl extends AbstractLldbModel implements DebuggerObjectMo @Override public void terminate() throws IOException { - listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); + listeners.invoke().modelClosed(DebuggerModelClosedReason.NORMAL); root.invalidateSubtree(root, "LLDB is terminating"); manager.terminate(); } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAbstractXpointSpec.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAbstractXpointSpec.java index eaf3f8e05c..5591660396 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAbstractXpointSpec.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetAbstractXpointSpec.java @@ -15,7 +15,8 @@ */ package agent.lldb.model.impl; -import java.util.*; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import SWIG.SBBreakpointLocation; @@ -27,23 +28,16 @@ import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet; import ghidra.dbg.target.schema.*; import ghidra.dbg.util.PathUtils; -import ghidra.util.datastruct.ListenerMap.ListenerEntry; import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.WeakValueHashMap; -@TargetObjectSchemaInfo( - name = "BreakpointSpec", - elements = { // - @TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class) - }, - attributes = { +@TargetObjectSchemaInfo(name = "BreakpointSpec", elements = { // + @TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class) }, attributes = { @TargetAttributeType(name = "Type", type = String.class), @TargetAttributeType(name = "Valid", type = Boolean.class), @TargetAttributeType(name = "Enabled", type = Boolean.class), @TargetAttributeType(name = "Count", type = Long.class), - @TargetAttributeType(type = Void.class) - }, - canonicalContainer = true) + @TargetAttributeType(type = Void.class) }, canonicalContainer = true) public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetObjectImpl implements LldbModelTargetBreakpointSpec { @@ -60,12 +54,7 @@ public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetO protected final Map breaksBySub = new WeakValueHashMap<>(); protected final ListenerSet actions = - new ListenerSet<>(TargetBreakpointAction.class) { - // Use strong references on actions - protected Map> createMap() { - return new LinkedHashMap<>(); - }; - }; + new ListenerSet<>(TargetBreakpointAction.class, false); public LldbModelTargetAbstractXpointSpec(LldbModelTargetBreakpointContainer breakpoints, Object info, String title) { @@ -159,7 +148,7 @@ public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetO protected void breakpointHit(LldbModelTargetStackFrame frame, LldbModelTargetBreakpointLocation eb) { - actions.fire.breakpointHit(this, frame.getParentThread(), frame, eb); + actions.invoke().breakpointHit(this, frame.getParentThread(), frame, eb); } public synchronized LldbModelTargetBreakpointLocation getTargetBreakpointLocation( @@ -180,7 +169,7 @@ public abstract class LldbModelTargetAbstractXpointSpec extends LldbModelTargetO @Override public ListenerSet getActions() { - return new ListenerSet(null); + return new ListenerSet(null, true); } } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetBreakpointSpecImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetBreakpointSpecImpl.java index 7cd67f78cd..f64653ce69 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetBreakpointSpecImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetBreakpointSpecImpl.java @@ -16,7 +16,8 @@ package agent.lldb.model.impl; import java.math.BigInteger; -import java.util.*; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import SWIG.SBBreakpoint; @@ -27,31 +28,19 @@ import ghidra.dbg.target.TargetBreakpointLocation; import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.*; -import ghidra.util.datastruct.ListenerMap.ListenerEntry; import ghidra.util.datastruct.ListenerSet; -@TargetObjectSchemaInfo( - name = "BreakpointSpec", - elements = { // - @TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class) - }, - attributes = { +@TargetObjectSchemaInfo(name = "BreakpointSpec", elements = { // + @TargetElementType(type = LldbModelTargetBreakpointLocationImpl.class) }, attributes = { @TargetAttributeType(name = "Type", type = String.class), @TargetAttributeType(name = "Valid", type = Boolean.class), @TargetAttributeType(name = "Enabled", type = Boolean.class), @TargetAttributeType(name = "Count", type = Long.class), - @TargetAttributeType(type = Void.class) - }, - canonicalContainer = true) + @TargetAttributeType(type = Void.class) }, canonicalContainer = true) public class LldbModelTargetBreakpointSpecImpl extends LldbModelTargetAbstractXpointSpec { protected final ListenerSet actions = - new ListenerSet<>(TargetBreakpointAction.class) { - // Use strong references on actions - protected Map> createMap() { - return new LinkedHashMap<>(); - }; - }; + new ListenerSet<>(TargetBreakpointAction.class, false); public LldbModelTargetBreakpointSpecImpl(LldbModelTargetBreakpointContainer breakpoints, Object info) { diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/DelegateGadpClientTargetObject.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/DelegateGadpClientTargetObject.java index 50e1d500b7..b7637ba8b6 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/DelegateGadpClientTargetObject.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/DelegateGadpClientTargetObject.java @@ -34,7 +34,6 @@ import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.program.model.address.AddressSpace; import ghidra.util.Msg; -import ghidra.util.datastruct.ListenerMap.ListenerEntry; import ghidra.util.datastruct.ListenerSet; import utilities.util.ProxyUtilities; @@ -60,8 +59,8 @@ public class DelegateGadpClientTargetObject for (Map.Entry ent : that.handles.entrySet()) { MethodHandle old = handles.put(ent.getKey(), ent.getValue()); if (old != null) { - throw new AssertionError("Conflict over handler for " + ent.getKey() + - ": " + old + " and " + ent.getValue()); + throw new AssertionError("Conflict over handler for " + ent.getKey() + ": " + + old + " and " + ent.getValue()); } } } @@ -161,8 +160,8 @@ public class DelegateGadpClientTargetObject Set> allMixins = new HashSet<>(mixins); allMixins.add(GadpClientTargetObject.class); - this.eventHandlers = EVENT_HANDLER_MAPS_BY_COMPOSITION.computeIfAbsent(allMixins, - GadpEventHandlerMap::new); + this.eventHandlers = + EVENT_HANDLER_MAPS_BY_COMPOSITION.computeIfAbsent(allMixins, GadpEventHandlerMap::new); } @Override @@ -187,11 +186,14 @@ public class DelegateGadpClientTargetObject @Override public CompletableFuture resync(RefreshBehavior attributes, RefreshBehavior elements) { - return client.sendChecked(Gadp.ResyncRequest.newBuilder() - .setPath(GadpValueUtils.makePath(path)) - .setAttributes(attributes.equals(RefreshBehavior.REFRESH_ALWAYS)) - .setElements(elements.equals(RefreshBehavior.REFRESH_ALWAYS)), - Gadp.ResyncReply.getDefaultInstance()).thenApply(rep -> null); + return client + .sendChecked( + Gadp.ResyncRequest.newBuilder() + .setPath(GadpValueUtils.makePath(path)) + .setAttributes(attributes.equals(RefreshBehavior.REFRESH_ALWAYS)) + .setElements(elements.equals(RefreshBehavior.REFRESH_ALWAYS)), + Gadp.ResyncReply.getDefaultInstance()) + .thenApply(rep -> null); } @Override @@ -238,9 +240,11 @@ public class DelegateGadpClientTargetObject public synchronized CompletableFuture invalidateCaches() { assertValid(); doClearCaches(); - return client.sendChecked(Gadp.CacheInvalidateRequest.newBuilder() - .setPath(GadpValueUtils.makePath(path)), - Gadp.CacheInvalidateReply.getDefaultInstance()).thenApply(rep -> null); + return client + .sendChecked( + Gadp.CacheInvalidateRequest.newBuilder().setPath(GadpValueUtils.makePath(path)), + Gadp.CacheInvalidateReply.getDefaultInstance()) + .thenApply(rep -> null); } protected synchronized CachedMemory getMemoryCache(AddressSpace space) { @@ -276,12 +280,7 @@ public class DelegateGadpClientTargetObject protected synchronized ListenerSet getActions(boolean createIfAbsent) { if (actions == null && createIfAbsent) { - actions = new ListenerSet<>(TargetBreakpointAction.class) { - // Want strong references on actions - protected Map> createMap() { - return new LinkedHashMap<>(); - }; - }; + actions = new ListenerSet<>(TargetBreakpointAction.class, false); } return actions; } diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClient.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClient.java index b1ebce65de..7e281d9d83 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClient.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClient.java @@ -323,10 +323,10 @@ public class GadpClient extends AbstractDebuggerObjectModel protected void channelStateChanged(ChannelState old, ChannelState set, DebuggerModelClosedReason reason) { if (old == ChannelState.NEGOTIATING && set == ChannelState.ACTIVE) { - listeners.fire.modelOpened(); + listeners.invoke().modelOpened(); } else if (old == ChannelState.ACTIVE && set == ChannelState.CLOSED) { - listeners.fire.modelClosed(reason); + listeners.invoke().modelClosed(reason); root.invalidateSubtree(root, "GADP Client disconnected"); messageMatcher.flush(new DebuggerModelTerminatingException("GADP Client disconnected")); } diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClientTargetBreakpointSpecContainer.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClientTargetBreakpointSpecContainer.java index 6bfe39fa0d..6721f02782 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClientTargetBreakpointSpecContainer.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClientTargetBreakpointSpecContainer.java @@ -77,7 +77,7 @@ public interface GadpClientTargetBreakpointSpecContainer GadpClientTargetBreakpointSpec specObj = (GadpClientTargetBreakpointSpec) spec; ListenerSet actions = specObj.getDelegate().getActions(false); if (actions != null) { - actions.fire.breakpointHit(specObj, trapped, frame, breakpoint); + actions.invoke().breakpointHit(specObj, trapped, frame, breakpoint); } } } diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventHandler.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventHandler.java index ac4eab5494..30304cc01b 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventHandler.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventHandler.java @@ -41,7 +41,7 @@ public class JdiEventHandler implements Runnable { protected final AsyncReference state = new AsyncReference<>(ThreadReference.THREAD_STATUS_NOT_STARTED); public final ListenerSet listenersEvent = - new ListenerSet<>(JdiEventsListener.class); + new ListenerSet<>(JdiEventsListener.class, true); protected final ExecutorService eventThread = Executors.newSingleThreadExecutor(); public JdiEventHandler() { @@ -116,7 +116,7 @@ public class JdiEventHandler implements Runnable { else if (eventSet.suspendPolicy() == EventRequest.SUSPEND_ALL) { setCurrentThread(eventSet); event( - () -> listenersEvent.fire.processStop(eventSet, JdiCause.Causes.UNCLAIMED), + () -> listenersEvent.invoke().processStop(eventSet, JdiCause.Causes.UNCLAIMED), "processStopped"); } } @@ -214,7 +214,7 @@ public class JdiEventHandler implements Runnable { /* * Inform jdb command line processor that jdb is being shutdown. JDK-8154144. */ - event(() -> listenersEvent.fire.processShutdown(event, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().processShutdown(event, JdiCause.Causes.UNCLAIMED), "processStopped"); return null; ///false; } @@ -301,7 +301,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processBreakpoint(BreakpointEvent evt) { - event(() -> listenersEvent.fire.breakpointHit(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().breakpointHit(evt, JdiCause.Causes.UNCLAIMED), "breakpointHit"); return DebugStatus.BREAK; } @@ -314,7 +314,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processException(ExceptionEvent evt) { - event(() -> listenersEvent.fire.exceptionHit(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().exceptionHit(evt, JdiCause.Causes.UNCLAIMED), "exceptionHit"); return DebugStatus.BREAK; } @@ -327,7 +327,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processMethodEntry(MethodEntryEvent evt) { - event(() -> listenersEvent.fire.methodEntry(evt, JdiCause.Causes.UNCLAIMED), "methodEntry"); + event(() -> listenersEvent.invoke().methodEntry(evt, JdiCause.Causes.UNCLAIMED), "methodEntry"); return DebugStatus.GO; } @@ -339,7 +339,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processMethodExit(MethodExitEvent evt) { - event(() -> listenersEvent.fire.methodExit(evt, JdiCause.Causes.UNCLAIMED), "methodExit"); + event(() -> listenersEvent.invoke().methodExit(evt, JdiCause.Causes.UNCLAIMED), "methodExit"); return DebugStatus.GO; } @@ -351,7 +351,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processClassPrepare(ClassPrepareEvent evt) { - event(() -> listenersEvent.fire.classPrepare(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().classPrepare(evt, JdiCause.Causes.UNCLAIMED), "classPrepare"); /* if (!Env.specList.resolve(cle)) { @@ -372,7 +372,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processClassUnload(ClassUnloadEvent evt) { - event(() -> listenersEvent.fire.classUnload(evt, JdiCause.Causes.UNCLAIMED), "classUnload"); + event(() -> listenersEvent.invoke().classUnload(evt, JdiCause.Causes.UNCLAIMED), "classUnload"); return DebugStatus.GO; } @@ -384,7 +384,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processMCEntered(MonitorContendedEnteredEvent evt) { - event(() -> listenersEvent.fire.monitorContendedEntered(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().monitorContendedEntered(evt, JdiCause.Causes.UNCLAIMED), "monitorContendedEntered"); return DebugStatus.GO; } @@ -397,7 +397,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processMCEnter(MonitorContendedEnterEvent evt) { - event(() -> listenersEvent.fire.monitorContendedEnter(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().monitorContendedEnter(evt, JdiCause.Causes.UNCLAIMED), "monitorContendedEnter"); return DebugStatus.GO; } @@ -410,7 +410,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processMonitorWaited(MonitorWaitedEvent evt) { - event(() -> listenersEvent.fire.monitorWaited(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().monitorWaited(evt, JdiCause.Causes.UNCLAIMED), "monitorWaited"); return DebugStatus.GO; } @@ -423,7 +423,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processMonitorWait(MonitorWaitEvent evt) { - event(() -> listenersEvent.fire.monitorWait(evt, JdiCause.Causes.UNCLAIMED), "monitorWait"); + event(() -> listenersEvent.invoke().monitorWait(evt, JdiCause.Causes.UNCLAIMED), "monitorWait"); return DebugStatus.GO; } @@ -436,7 +436,7 @@ public class JdiEventHandler implements Runnable { */ protected DebugStatus processStep(StepEvent evt) { evt.request().disable(); - event(() -> listenersEvent.fire.stepComplete(evt, JdiCause.Causes.UNCLAIMED), "step"); + event(() -> listenersEvent.invoke().stepComplete(evt, JdiCause.Causes.UNCLAIMED), "step"); return DebugStatus.STEP_INTO; } @@ -448,7 +448,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processWatchpoint(WatchpointEvent evt) { - event(() -> listenersEvent.fire.watchpointHit(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().watchpointHit(evt, JdiCause.Causes.UNCLAIMED), "watchpointHit"); return DebugStatus.BREAK; } @@ -461,7 +461,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processAccessWatchpoint(AccessWatchpointEvent evt) { - event(() -> listenersEvent.fire.accessWatchpointHit(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().accessWatchpointHit(evt, JdiCause.Causes.UNCLAIMED), "accessWatchpointHit"); return DebugStatus.BREAK; } @@ -474,7 +474,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processWatchpointModification(ModificationWatchpointEvent evt) { - event(() -> listenersEvent.fire.watchpointModified(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().watchpointModified(evt, JdiCause.Causes.UNCLAIMED), "watchpointModified"); return DebugStatus.GO; } @@ -487,7 +487,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processThreadDeath(ThreadDeathEvent evt) { - event(() -> listenersEvent.fire.threadExited(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().threadExited(evt, JdiCause.Causes.UNCLAIMED), "threadExited"); JdiThreadInfo.removeThread(evt.thread()); return DebugStatus.GO; @@ -502,7 +502,7 @@ public class JdiEventHandler implements Runnable { */ protected DebugStatus processThreadStart(ThreadStartEvent evt) { JdiThreadInfo.addThread(evt.thread()); - event(() -> listenersEvent.fire.threadStarted(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().threadStarted(evt, JdiCause.Causes.UNCLAIMED), "threadStarted"); return DebugStatus.GO; } @@ -516,7 +516,7 @@ public class JdiEventHandler implements Runnable { */ protected DebugStatus processVMDeath(VMDeathEvent evt) { shutdownMessageKey = "The application exited"; - event(() -> listenersEvent.fire.vmDied(evt, JdiCause.Causes.UNCLAIMED), "vmDied"); + event(() -> listenersEvent.invoke().vmDied(evt, JdiCause.Causes.UNCLAIMED), "vmDied"); return DebugStatus.BREAK; } @@ -529,7 +529,7 @@ public class JdiEventHandler implements Runnable { */ protected DebugStatus processVMDisconnect(VMDisconnectEvent evt) { shutdownMessageKey = "The application has been disconnected"; - event(() -> listenersEvent.fire.vmDisconnected(evt, JdiCause.Causes.UNCLAIMED), + event(() -> listenersEvent.invoke().vmDisconnected(evt, JdiCause.Causes.UNCLAIMED), "vmDisconnected"); return DebugStatus.BREAK; } @@ -542,7 +542,7 @@ public class JdiEventHandler implements Runnable { * @return */ protected DebugStatus processVMStart(VMStartEvent evt) { - event(() -> listenersEvent.fire.vmStarted(evt, JdiCause.Causes.UNCLAIMED), "vmStarted"); + event(() -> listenersEvent.invoke().vmStarted(evt, JdiCause.Causes.UNCLAIMED), "vmStarted"); return DebugStatus.BREAK; } diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/impl/JdiManagerImpl.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/impl/JdiManagerImpl.java index a049ec8291..2f58b67e13 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/impl/JdiManagerImpl.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/impl/JdiManagerImpl.java @@ -41,9 +41,9 @@ public class JdiManagerImpl implements JdiManager { private final Map unmodifiableVMs = Collections.unmodifiableMap(vms); protected final ListenerSet listenersTargetOutput = - new ListenerSet<>(JdiTargetOutputListener.class); + new ListenerSet<>(JdiTargetOutputListener.class, true); protected final ListenerSet listenersConsoleOutput = - new ListenerSet<>(JdiConsoleOutputListener.class); + new ListenerSet<>(JdiConsoleOutputListener.class, true); protected final ExecutorService eventThread = Executors.newSingleThreadExecutor(); protected JdiEventHandler globalEventHandler = new JdiEventHandler(); diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointSpec.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointSpec.java index c6833282f2..4fd166e87b 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointSpec.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointSpec.java @@ -15,7 +15,8 @@ */ package ghidra.dbg.jdi.model; -import java.util.*; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; @@ -26,21 +27,12 @@ import ghidra.dbg.target.TargetBreakpointSpec; import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet; import ghidra.dbg.target.schema.TargetAttributeType; import ghidra.dbg.target.schema.TargetObjectSchemaInfo; -import ghidra.util.datastruct.ListenerMap.ListenerEntry; import ghidra.util.datastruct.ListenerSet; -@TargetObjectSchemaInfo( - name = "BreakpointSpec", - attributes = { - @TargetAttributeType( - name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, - type = JdiModelTargetBreakpointContainer.class), - @TargetAttributeType( - name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, - type = JdiModelTargetBreakpointSpec.class), - @TargetAttributeType(type = Void.class) - }, - canonicalContainer = true) +@TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = { + @TargetAttributeType(name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, type = JdiModelTargetBreakpointContainer.class), + @TargetAttributeType(name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, type = JdiModelTargetBreakpointSpec.class), + @TargetAttributeType(type = Void.class) }, canonicalContainer = true) public class JdiModelTargetBreakpointSpec extends JdiModelTargetObjectImpl implements TargetBreakpointSpec, JdiModelTargetDeletable { @@ -48,12 +40,7 @@ public class JdiModelTargetBreakpointSpec extends JdiModelTargetObjectImpl protected TargetBreakpointKindSet kinds; protected final ListenerSet actions = - new ListenerSet<>(TargetBreakpointAction.class) { - // Use strong references on actions - protected Map> createMap() { - return new LinkedHashMap<>(); - } - }; + new ListenerSet<>(TargetBreakpointAction.class, false); public JdiModelTargetBreakpointSpec(JdiModelTargetBreakpointContainer breakpoints, JdiBreakpointInfo info, boolean isElement) { diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java index 02c9681df4..f6c9b7e6ec 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java @@ -274,7 +274,7 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen } targetVM.vmStateChanged(targetState, reason); JdiEventHandler eventHandler = getManager().getEventHandler(targetVM.vm); - eventHandler.listenersEvent.fire.threadStateChanged(thread, state, + eventHandler.listenersEvent.invoke().threadStateChanged(thread, state, JdiCause.Causes.UNCLAIMED, reason); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java index 0ea8961f39..739106df93 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java @@ -217,7 +217,7 @@ public class DebuggerListingProvider extends CodeViewerProvider { trackingLabel.setText(""); trackingLabel.setToolTipText(""); trackingLabel.setForeground(Colors.FOREGROUND); - trackingSpecChangeListeners.fire.locationTrackingSpecChanged(spec); + trackingSpecChangeListeners.invoke().locationTrackingSpecChanged(spec); } @Override @@ -291,7 +291,7 @@ public class DebuggerListingProvider extends CodeViewerProvider { protected final ForListingReadsMemoryTrait readsMemTrait; protected final ListenerSet trackingSpecChangeListeners = - new ListenerSet<>(LocationTrackingSpecChangeListener.class); + new ListenerSet<>(LocationTrackingSpecChangeListener.class, true); protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel(); protected final JLabel trackingLabel = new JLabel(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java index 2c21024c7f..90d60d5bae 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java @@ -51,7 +51,7 @@ public abstract class AbstractQueryTablePanel cellActivationListeners = - new ListenerSet<>(CellActivationListener.class); + new ListenerSet<>(CellActivationListener.class, true); public AbstractQueryTablePanel(Plugin plugin) { super(new BorderLayout()); @@ -236,6 +236,6 @@ public abstract class AbstractQueryTablePanel changeListeners = - new ListenerSet<>(LogicalBreakpointsChangeListener.class); + new ListenerSet<>(LogicalBreakpointsChangeListener.class, true); private final TrackRecordersListener targetsListener = new TrackRecordersListener(); private final TrackMappingsListener mappingListener = new TrackMappingsListener(); @@ -835,7 +835,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin protected void processChange(Consumer processor, String description) { executor.submit(() -> { // Invoke change callbacks without the lock! (try must surround sync) - try (ChangeCollector c = new ChangeCollector(changeListeners.fire)) { + try (ChangeCollector c = new ChangeCollector(changeListeners.invoke())) { synchronized (lock) { processor.accept(c); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java index 9c04e11d7c..4b9b2bbba6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java @@ -213,7 +213,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin private final Map currentModes = new HashMap<>(); private final ListenerSet listeners = - new ListenerSet<>(ControlModeChangeListener.class); + new ListenerSet<>(ControlModeChangeListener.class, true); @Override public ControlMode getCurrentMode(Trace trace) { @@ -232,7 +232,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin } } if (newMode != oldMode) { - listeners.fire.modeChanged(trace, newMode); + listeners.invoke().modeChanged(trace, newMode); tool.contextChanged(null); } } @@ -280,7 +280,7 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin } } if (newMode != oldMode) { - listeners.fire.modeChanged(trace, newMode); + listeners.invoke().modeChanged(trace, newMode); tool.contextChanged(null); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java index 1d51f6f86e..a97b476824 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java @@ -295,7 +295,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm .forgetValues((key, l) -> true); protected final Map busy = new LinkedHashMap<>(); protected final ListenerSet stateListeners = - new ListenerSet<>(EmulatorStateListener.class); + new ListenerSet<>(EmulatorStateListener.class, true); class BusyEmu implements AutoCloseable { private final CachedEmulator ce; @@ -314,7 +314,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } } if (fire) { - stateListeners.fire.running(ce); + stateListeners.invoke().running(ce); } } @@ -332,7 +332,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } } if (fire) { - stateListeners.fire.stopped(ce); + stateListeners.invoke().stopped(ce); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java index 578588a6f9..eea5e338a4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java @@ -95,7 +95,7 @@ public class DebuggerModelServicePlugin extends Plugin models.remove(model); } model.removeModelListener(this); - modelListeners.fire.elementRemoved(model); + modelListeners.invoke().elementRemoved(model); if (currentModel == model) { activateModel(null); } @@ -155,11 +155,11 @@ public class DebuggerModelServicePlugin extends Plugin protected final Map recordersByTarget = new WeakHashMap<>(); protected final ListenerSet> factoryListeners = - new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class)); + new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class), true); protected final ListenerSet> modelListeners = - new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class)); + new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class), true); protected final ListenerSet> recorderListeners = - new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class)); + new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class), true); protected final ChangeListener classChangeListener = new ChangeListenerForFactoryInstances(); protected final ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders(); @@ -264,7 +264,7 @@ public class DebuggerModelServicePlugin extends Plugin "Invalidated before or during add to service"); } } - modelListeners.fire.elementAdded(model); + modelListeners.invoke().elementAdded(model); return true; } @@ -276,7 +276,7 @@ public class DebuggerModelServicePlugin extends Plugin return false; } } - modelListeners.fire.elementRemoved(model); + modelListeners.invoke().elementRemoved(model); return true; } @@ -315,7 +315,7 @@ public class DebuggerModelServicePlugin extends Plugin }); recordersByTarget.put(target, recorder); } - recorderListeners.fire.elementAdded(recorder); + recorderListeners.invoke().elementAdded(recorder); // NOTE: It's possible the recorder stopped recording before we installed the listener if (!recorder.isRecording()) { doRemoveRecorder(recorder); @@ -385,7 +385,7 @@ public class DebuggerModelServicePlugin extends Plugin } old.removeListener(listenerOnRecorders); } - recorderListeners.fire.elementRemoved(recorder); + recorderListeners.invoke().elementRemoved(recorder); } @Override @@ -423,7 +423,7 @@ public class DebuggerModelServicePlugin extends Plugin diff.removeAll(newFactories); for (DebuggerModelFactory factory : diff) { factories.remove(factory); - factoryListeners.fire.elementRemoved(factory); + factoryListeners.invoke().elementRemoved(factory); } diff.clear(); @@ -431,7 +431,7 @@ public class DebuggerModelServicePlugin extends Plugin diff.removeAll(factories); for (DebuggerModelFactory factory : diff) { factories.add(factory); - factoryListeners.fire.elementAdded(factory); + factoryListeners.invoke().elementAdded(factory); } } @@ -525,7 +525,7 @@ public class DebuggerModelServicePlugin extends Plugin removed = recordersByTarget.remove(recorder.getTarget()) != null; } if (removed) { - recorderListeners.fire.elementRemoved(recorder); + recorderListeners.invoke().elementRemoved(recorder); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java index 191515f70c..58fca8491c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java @@ -72,23 +72,10 @@ import ghidra.util.datastruct.ListenerSet; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -@PluginInfo( - shortDescription = "Debugger models manager service (proxy to front-end)", - description = "Manage debug sessions, connections, and trace recording", - category = PluginCategoryNames.DEBUGGER, - packageName = DebuggerPluginPackage.NAME, - status = PluginStatus.RELEASED, - eventsConsumed = { - ProgramActivatedPluginEvent.class, - ProgramClosedPluginEvent.class, - }, - servicesRequired = { +@PluginInfo(shortDescription = "Debugger models manager service (proxy to front-end)", description = "Manage debug sessions, connections, and trace recording", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, eventsConsumed = { + ProgramActivatedPluginEvent.class, ProgramClosedPluginEvent.class, }, servicesRequired = { DebuggerTargetService.class, - DebuggerTraceManagerService.class, - }, - servicesProvided = { - DebuggerModelService.class, - }) + DebuggerTraceManagerService.class, }, servicesProvided = { DebuggerModelService.class, }) public class DebuggerModelServiceProxyPlugin extends Plugin implements DebuggerModelServiceInternal { @@ -158,17 +145,17 @@ public class DebuggerModelServiceProxyPlugin extends Plugin implements CollectionChangeListener { @Override public void elementAdded(DebuggerModelFactory element) { - factoryListeners.fire.elementAdded(element); + factoryListeners.invoke().elementAdded(element); } @Override public void elementRemoved(DebuggerModelFactory element) { - factoryListeners.fire.elementRemoved(element); + factoryListeners.invoke().elementRemoved(element); } @Override public void elementModified(DebuggerModelFactory element) { - factoryListeners.fire.elementModified(element); + factoryListeners.invoke().elementModified(element); } } @@ -176,7 +163,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin implements CollectionChangeListener { @Override public void elementAdded(DebuggerObjectModel element) { - modelListeners.fire.elementAdded(element); + modelListeners.invoke().elementAdded(element); if (currentModel == null) { activateModel(element); } @@ -187,12 +174,12 @@ public class DebuggerModelServiceProxyPlugin extends Plugin if (currentModel == element) { activateModel(null); } - modelListeners.fire.elementRemoved(element); + modelListeners.invoke().elementRemoved(element); } @Override public void elementModified(DebuggerObjectModel element) { - modelListeners.fire.elementModified(element); + modelListeners.invoke().elementModified(element); } } @@ -200,7 +187,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin implements CollectionChangeListener { @Override public void elementAdded(TraceRecorder element) { - recorderListeners.fire.elementAdded(element); + recorderListeners.invoke().elementAdded(element); Swing.runIfSwingOrRunLater(() -> { TraceRecorderTarget target = new TraceRecorderTarget(tool, element); targets.put(element, target); @@ -210,15 +197,16 @@ public class DebuggerModelServiceProxyPlugin extends Plugin @Override public void elementRemoved(TraceRecorder element) { - recorderListeners.fire.elementRemoved(element); + recorderListeners.invoke().elementRemoved(element); Swing.runIfSwingOrRunLater(() -> { targetService.withdrawTarget(Objects.requireNonNull(targets.get(element))); }); + } @Override public void elementModified(TraceRecorder element) { - recorderListeners.fire.elementModified(element); + recorderListeners.invoke().elementModified(element); } } @@ -249,11 +237,11 @@ public class DebuggerModelServiceProxyPlugin extends Plugin DockingAction actionDisconnectAll; protected final ListenerSet> factoryListeners = - new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class)); + new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class), true); protected final ListenerSet> modelListeners = - new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class)); + new ListenerSet<>(CollectionChangeListener.of(DebuggerObjectModel.class), true); protected final ListenerSet> recorderListeners = - new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class)); + new ListenerSet<>(CollectionChangeListener.of(TraceRecorder.class), true); protected final Map targets = new HashMap<>(); @@ -278,8 +266,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin // Note, I have to give an enabledWhen, otherwise any context change re-enables it MultiStateActionBuilder builderDebugProgram = DebugProgramAction.buttonBuilder(this, delegate); - actionDebugProgram = builderDebugProgram - .enabledWhen(ctx -> currentProgram != null) + actionDebugProgram = builderDebugProgram.enabledWhen(ctx -> currentProgram != null) .onAction(this::debugProgramButtonActivated) .onActionStateChanged(this::debugProgramStateActivated) .addState(DUMMY_LAUNCH_STATE) @@ -333,8 +320,8 @@ public class DebuggerModelServiceProxyPlugin extends Plugin protected void writeMostRecentLaunches(Program program, List mrl) { ProgramUserData userData = program.getProgramUserData(); try (Transaction tid = userData.openTransaction()) { - StringPropertyMap prop = userData - .getStringProperty(getName(), KEY_MOST_RECENT_LAUNCHES, true); + StringPropertyMap prop = + userData.getStringProperty(getName(), KEY_MOST_RECENT_LAUNCHES, true); Address min = program.getAddressFactory().getDefaultAddressSpace().getMinAddress(); prop.add(min, mrl.stream().collect(Collectors.joining(";"))); } @@ -425,8 +412,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin List offers = program == null ? List.of() : getProgramLaunchOffers(program).collect(Collectors.toList()); List> states = offers.stream() - .map(o -> new ActionState<>(o.getButtonTitle(), - o.getIcon(), o)) + .map(o -> new ActionState<>(o.getButtonTitle(), o.getIcon(), o)) .collect(Collectors.toList()); if (!states.isEmpty()) { actionDebugProgram.setActionStates(states); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultProcessRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultProcessRecorder.java index 1ad4ccfbb2..e2891b5132 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultProcessRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultProcessRecorder.java @@ -46,7 +46,7 @@ public class DefaultProcessRecorder implements ManagedProcessRecorder { protected void processMemoryAccessibilityChanged(boolean old, boolean acc, Void __) { - recorder.getListeners().fire.processMemoryAccessibilityChanged(recorder); + recorder.getListeners().invoke().processMemoryAccessibilityChanged(recorder); } public CompletableFuture readProcessMemory(Address start, int length) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java index 4f379c53ba..45f6d234e6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java @@ -154,7 +154,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { return AsyncUtils.nil(); } return initRegMapper(descs).thenAccept(__ -> { - recorder.getListeners().fire.registerBankMapped(recorder); + recorder.getListeners().invoke().registerBankMapped(recorder); }).exceptionally(ex -> { Msg.error(this, "Could not intialize register mapper", ex); return null; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTimeRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTimeRecorder.java index 92e490b4d0..37a39dafd5 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTimeRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTimeRecorder.java @@ -52,7 +52,7 @@ public class DefaultTimeRecorder { RecorderPermanentTransaction tid) { if (tid != null) { doAdvanceSnap(description, eventThread); - recorder.getListeners().fire.snapAdvanced(recorder, getSnap()); + recorder.getListeners().invoke().snapAdvanced(recorder, getSnap()); return; } // NB. The also serves as the snap counter, so it must be on the service thread @@ -60,6 +60,6 @@ public class DefaultTimeRecorder { RecorderPermanentTransaction.start(trace, description)) { doAdvanceSnap(description, eventThread); } - recorder.getListeners().fire.snapAdvanced(recorder, getSnap()); + recorder.getListeners().invoke().snapAdvanced(recorder, getSnap()); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java index b5d3e69c41..1dd3e6cd7c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java @@ -367,7 +367,7 @@ public class DefaultTraceRecorder implements TraceRecorder { @Override public void stopRecording() { invalidate(); - getListeners().fire.recordingStopped(this); + getListeners().invoke().recordingStopped(this); } protected void invalidate() { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java index 22f93f6743..66010922c8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java @@ -58,7 +58,7 @@ public class TraceObjectManager { //private AbstractRecorderRegisterSet threadRegisters; private final ListenerSet listeners = - new ListenerSet<>(TraceRecorderListener.class); + new ListenerSet<>(TraceRecorderListener.class, true); protected final Set breakpoints = new HashSet<>(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java index c5f9788b76..4562789ada 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java @@ -81,7 +81,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { protected final ListenerForRecord listenerForRecord; protected final ListenerSet listeners = - new ListenerSet<>(TraceRecorderListener.class); + new ListenerSet<>(TraceRecorderListener.class, true); // TODO: I don't like this here. Should ask the model, not the recorder. protected TargetObject curFocus; @@ -805,11 +805,11 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { } protected void fireSnapAdvanced(long key) { - listeners.fire.snapAdvanced(this, key); + listeners.invoke().snapAdvanced(this, key); } protected void fireRecordingStopped() { - listeners.fire.recordingStopped(this); + listeners.invoke().recordingStopped(this); } // TODO: Deprecate/remove the other callbacks: registerBankMapped, *accessibilityChanged diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java index 8bfb56affe..7fcb9665e0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java @@ -544,7 +544,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin private final AsyncDebouncer changeDebouncer = new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100); private final ListenerSet changeListeners = - new ListenerSet<>(DebuggerStaticMappingChangeListener.class); + new ListenerSet<>(DebuggerStaticMappingChangeListener.class, true); private Set affectedTraces = new HashSet<>(); private Set affectedPrograms = new HashSet<>(); @@ -576,7 +576,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin affectedTraces = new HashSet<>(); affectedPrograms = new HashSet<>(); } - changeListeners.fire.mappingsChanged(traces, programs); + changeListeners.invoke().mappingsChanged(traces, programs); } private void traceAffected(Trace trace) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/DebuggerTargetServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/DebuggerTargetServicePlugin.java index bca06c9c99..d2a620cb0a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/DebuggerTargetServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/DebuggerTargetServicePlugin.java @@ -27,15 +27,8 @@ import ghidra.framework.plugintool.util.PluginStatus; import ghidra.trace.model.Trace; import ghidra.util.datastruct.ListenerSet; -@PluginInfo( - shortDescription = "Debugger targets manager service", - description = "Maintains a collection of published targets and notifies listeners of changes.", - category = PluginCategoryNames.DEBUGGER, - packageName = DebuggerPluginPackage.NAME, - status = PluginStatus.RELEASED, - servicesProvided = { - DebuggerTargetService.class, - }) +@PluginInfo(shortDescription = "Debugger targets manager service", description = "Maintains a collection of published targets and notifies listeners of changes.", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, servicesProvided = { + DebuggerTargetService.class, }) public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTargetService { public DebuggerTargetServicePlugin(PluginTool tool) { @@ -44,7 +37,7 @@ public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTarge private final Map targets = new HashMap<>(); private final ListenerSet listeners = - new ListenerSet<>(TargetPublicationListener.class); + new ListenerSet<>(TargetPublicationListener.class, true); @Override public void publishTarget(Target target) { @@ -53,7 +46,7 @@ public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTarge notify = targets.put(target.getTrace(), target) != target; } if (notify) { - listeners.fire.targetPublished(target); + listeners.invoke().targetPublished(target); } } @@ -64,7 +57,7 @@ public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTarge notify = targets.remove(target.getTrace()) == target; } if (notify) { - listeners.fire.targetWithdrawn(target); + listeners.invoke().targetWithdrawn(target); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java index 777e7562d3..5c7452fe21 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java @@ -34,8 +34,6 @@ import javax.swing.tree.TreePath; import org.junit.*; import org.junit.rules.TestName; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; import db.Transaction; import docking.ActionContext; @@ -81,7 +79,7 @@ import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceAddressSpace; import ghidra.util.InvalidNameException; -import ghidra.util.datastruct.ListenerMap; +import ghidra.util.datastruct.TestDataStructureErrorHandlerInstaller; import ghidra.util.exception.CancelledException; import ghidra.util.task.ConsoleTaskMonitor; @@ -103,8 +101,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest } @Override - protected DebuggerRegisterMapper createRegisterMapper( - TargetRegisterContainer registers) { + protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) { return new DefaultDebuggerRegisterMapper(cSpec, registers, true); } } @@ -142,7 +139,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest /** * Works like {@link #waitForValue(Supplier)}, except this caches {@link NoSuchElementException} * and tries again. - * + * * @param the type of object to wait for * @param supplier the supplier of the object * @return the object @@ -172,7 +169,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest /** * This is so gross - * + * * @param lockable */ protected void waitForLock(DomainObject lockable) { @@ -184,7 +181,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest /** * Get an address in the trace's default space - * + * * @param trace the trace * @param offset the byte offset in the default space * @return the address @@ -195,7 +192,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest /** * Get an address in the program's default space - * + * * @param program the program * @param offset the byte offset in the default space * @return the address @@ -206,7 +203,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest /** * Get an address range in the trace's default space - * + * * @param program the program * @param min the min byte offset in the default space * @param max the max byte offset in the default space @@ -350,10 +347,10 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest /** * Find the sub menu item of the current selection by text - * + * * Note that if the desired item is at the same level as the currently selected item, this * method will not find it. It searches the sub menu of the currently selected item. - * + * * @param text the text * @return the found item * @throws NoSuchElementException if the desired item is not found @@ -365,7 +362,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest /** * Activate via mouse the sub menu item of the current selection by text - * + * * @param text the text on the item to click * @throws AWTException * @throws NoSuchElementException if no item with the given text is found @@ -481,9 +478,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest protected static void performEnabledAction(ActionContextProvider provider, DockingActionIf action, boolean wait) { ActionContext context = waitForValue(() -> { - ActionContext ctx = provider == null - ? new DefaultActionContext() - : provider.getActionContext(null); + ActionContext ctx = + provider == null ? new DefaultActionContext() : provider.getActionContext(null); if (!action.isEnabledForContext(ctx)) { return null; } @@ -556,15 +552,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest @Rule public TestName name = new TestName(); - @Rule - public TestWatcher watcher = new TestWatcher() { - @Override - protected void succeeded(Description description) { - if (description.isTest()) { - ListenerMap.checkErr(); - } - } - }; + protected final ConsoleTaskMonitor monitor = new ConsoleTaskMonitor(); protected void waitRecorder(TraceRecorder recorder) throws Throwable { @@ -586,9 +574,16 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest waitForDomainObject(recorder.getTrace()); } + @BeforeClass + public static void beforeClass() { + + // Note: we may decided to move this up to a framework-level base test class + TestDataStructureErrorHandlerInstaller.installConcurrentExceptionErrorHandler(); + } + @Before public void setUp() throws Exception { - ListenerMap.clearErr(); + env = new TestEnv(); tool = env.getTool(); @@ -670,8 +665,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest populateTestModel(); TargetObject target = chooseTarget(); - TraceRecorder recorder = modelService.recordTarget(target, - createTargetTraceMapper(target), ActionSource.AUTOMATIC); + TraceRecorder recorder = modelService.recordTarget(target, createTargetTraceMapper(target), + ActionSource.AUTOMATIC); waitRecorder(recorder); return recorder; @@ -682,9 +677,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest protected void intoProject(DomainObject obj) { waitForDomainObject(obj); - DomainFolder rootFolder = tool.getProject() - .getProjectData() - .getRootFolder(); + DomainFolder rootFolder = tool.getProject().getProjectData().getRootFolder(); waitForCondition(() -> { try { rootFolder.createFile(obj.getName(), obj, monitor); @@ -799,8 +792,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged); } - void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range, - byte[] oldValue, byte[] newValue) { + void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range, byte[] oldValue, + byte[] newValue) { if (space.getThread() != traceThread) { return; } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java index 2750350efd..790ac2d4f8 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java @@ -36,8 +36,8 @@ import ghidra.dbg.target.*; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; import ghidra.debug.api.action.ActionSource; import ghidra.debug.api.breakpoint.LogicalBreakpoint; -import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener; import ghidra.debug.api.breakpoint.LogicalBreakpoint.State; +import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener; import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.model.TraceRecorder; import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener; @@ -53,7 +53,6 @@ import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.modules.TraceStaticMapping; import ghidra.util.Msg; import ghidra.util.SystemUtilities; -import ghidra.util.datastruct.ListenerMap; public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDebuggerGUITest { protected static final long TIMEOUT_MILLIS = @@ -61,14 +60,13 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe /** * Tracks the current set of logical breakpoints. - * + * *

* Its assertions require perfection in the sequence of events: 1) No double-adds. 2) No * double-removes. 3) No extraneous updates. At the end of each test, the current set of * breakpoints in this listener should be verified against those reported by the service. */ - protected class NoDuplicatesTrackingChangeListener - implements LogicalBreakpointsChangeListener { + protected class NoDuplicatesTrackingChangeListener implements LogicalBreakpointsChangeListener { private Set current = new HashSet<>(); @Override @@ -152,8 +150,6 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe @Before public void setUpBreakpointServiceTest() throws Throwable { - ListenerMap.clearErr(); - addPlugin(tool, DebuggerLogicalBreakpointServicePlugin.class); breakpointService = tool.getService(DebuggerLogicalBreakpointService.class); mappingService = tool.getService(DebuggerStaticMappingService.class); @@ -197,7 +193,6 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe waitForLock(recorder3.getTrace()); recorder3.stopRecording(); } - ListenerMap.checkErr(); } catch (Throwable t) { Msg.error(this, "Failed during tear down: " + t); @@ -226,8 +221,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe Map> breaksByProgramViaPer = new HashMap<>(); for (Program prog : programManager.getAllOpenPrograms()) { Set breaks = new HashSet<>(); - for (Entry> ent : breakpointService - .getBreakpoints(prog) + for (Entry> ent : breakpointService.getBreakpoints(prog) .entrySet()) { for (LogicalBreakpoint lb : ent.getValue()) { ProgramLocation loc = lb.getProgramLocation(); @@ -264,8 +258,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe protected void addProgramTextBlock(Program p) throws Throwable { try (Transaction tx = program.openTransaction("Add .text block")) { p.getMemory() - .createInitializedBlock(".text", addr(p, 0x00400000), 0x1000, (byte) 0, - monitor, false); + .createInitializedBlock(".text", addr(p, 0x00400000), 0x1000, (byte) 0, monitor, + false); } } @@ -290,8 +284,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe DebuggerStaticMappingUtils.addMapping( new DefaultTraceLocation(t, null, textRegion.getLifespan(), textRegion.getMinAddress()), - new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, - false); + new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false); } } @@ -329,8 +322,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe TargetBreakpointSpecContainer cont = getBreakpointContainer(r); cont.fetchElements().thenAccept(elements -> { for (TargetObject obj : elements.values()) { - if (!(obj instanceof TargetBreakpointSpec) || - !(obj instanceof TargetDeletable)) { + if (!(obj instanceof TargetBreakpointSpec) || !(obj instanceof TargetDeletable)) { continue; } TargetBreakpointSpec spec = (TargetBreakpointSpec) obj; @@ -394,8 +386,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe int total) { assertEquals(total, breakpointService.getAllBreakpoints().size()); - LogicalBreakpoint enLb = Unique - .assertOne(breakpointService.getBreakpointsAt(trace, addr(trace, offset))); + LogicalBreakpoint enLb = + Unique.assertOne(breakpointService.getBreakpointsAt(trace, addr(trace, offset))); assertNull(enLb.getProgramLocation()); assertEquals(Set.of(TraceBreakpointKind.SW_EXECUTE), enLb.getKinds()); @@ -958,10 +950,11 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe addTextMapping(recorder3, text3, program); waitForSwing(); waitForPass(() -> { - assertEquals(2, mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); + assertEquals(2, + mappingService + .getOpenMappedLocations( + new ProgramLocation(program, addr(program, 0x00400123))) + .size()); }); waitForSwing(); @@ -997,10 +990,11 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe addTextMapping(recorder3, text3, program); waitForSwing(); waitForPass(() -> { - assertEquals(2, mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); + assertEquals(2, + mappingService + .getOpenMappedLocations( + new ProgramLocation(program, addr(program, 0x00400123))) + .size()); }); waitForSwing(); @@ -1041,10 +1035,11 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe addTextMapping(recorder3, text3, program); waitForSwing(); waitForPass(() -> { - assertEquals(2, mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); + assertEquals(2, + mappingService + .getOpenMappedLocations( + new ProgramLocation(program, addr(program, 0x00400123))) + .size()); }); waitForSwing(); @@ -1094,10 +1089,11 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe addTextMapping(recorder3, text3, program); waitForSwing(); waitForPass(() -> { - assertEquals(2, mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); + assertEquals(2, + mappingService + .getOpenMappedLocations( + new ProgramLocation(program, addr(program, 0x00400123))) + .size()); }); waitForSwing(); @@ -1130,7 +1126,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe * breakpoints, so there's no context in which testing this service with aborted transactions on * breakpoints is sane. */ - //@Test + //@Test public void testAbortAddBreakpoint() throws Throwable { startRecorder1(); Trace trace = recorder1.getTrace(); @@ -1548,8 +1544,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe DebuggerStaticMappingUtils.addMapping( new DefaultTraceLocation(tb.trace, null, textRegion.getLifespan(), textRegion.getMinAddress()), - new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, - false); + new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false); } } @@ -1621,8 +1616,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe @Test public void testAddTraceBreakpointSetSleighThenMapThenSaveToProgramCopiesSleigh() throws Throwable { - DebuggerControlService editingService = - addPlugin(tool, DebuggerControlServicePlugin.class); + DebuggerControlService editingService = addPlugin(tool, DebuggerControlServicePlugin.class); // TODO: What if already mapped? // Not sure I care about tb.setEmuSleigh() out of band @@ -1637,8 +1631,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe try (Transaction tid = tb.startTransaction()) { TraceBreakpoint bpt = tb.trace.getBreakpointManager() .addBreakpoint("Processes[1].Breakpoints[0]", Lifespan.nowOn(0), - tb.addr(0x55550123), - Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE), + tb.addr(0x55550123), Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE), false /* emuEnabled defaults to true */, ""); bpt.setEmuSleigh("r0=0xbeef;"); } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractDebuggerObjectModel.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractDebuggerObjectModel.java index c90772cf20..3b3d7d6856 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractDebuggerObjectModel.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractDebuggerObjectModel.java @@ -32,12 +32,12 @@ import ghidra.util.datastruct.ListenerSet; public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectModel { public final Object lock = new Object(); public final Object cbLock = new Object(); - protected final ExecutorService clientExecutor = - Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() - .namingPattern(getClass().getSimpleName() + "-thread-%d") + protected final ExecutorService clientExecutor = Executors.newSingleThreadExecutor( + new BasicThreadFactory.Builder().namingPattern(getClass().getSimpleName() + "-thread-%d") .build()); + protected final ListenerSet listeners = - new ListenerSet<>(DebuggerModelListener.class, clientExecutor); + new ListenerSet<>(DebuggerModelListener.class, true); protected SpiTargetObject root; protected boolean rootAdded; @@ -98,7 +98,10 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo return null; }); this.completedRoot.completeAsync(() -> root, clientExecutor); - listeners.fire.rootAdded(root); + + clientExecutor.execute(() -> { + listeners.invoke().rootAdded(root); + }); } } @@ -200,7 +203,7 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo /** * Ensure that dependent computations occur on the client executor - * + * * @param the type of the future value * @param v the future * @return a future which completes after the given one on the client executor @@ -249,13 +252,11 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo } DefaultTargetObject dtoParent = (DefaultTargetObject) delegate; if (PathUtils.isIndex(path)) { - dtoParent.changeElements(List.of(PathUtils.getIndex(path)), List.of(), - "Replaced"); + dtoParent.changeElements(List.of(PathUtils.getIndex(path)), List.of(), "Replaced"); } else { assert PathUtils.isName(path); - dtoParent.changeAttributes(List.of(PathUtils.getKey(path)), Map.of(), - "Replaced"); + dtoParent.changeAttributes(List.of(PathUtils.getKey(path)), Map.of(), "Replaced"); } } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractTargetObject.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractTargetObject.java index a466df71a9..b315bb1d4d 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractTargetObject.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/AbstractTargetObject.java @@ -304,6 +304,6 @@ public abstract class AbstractTargetObject

implements Sp @Override public DebuggerModelListener broadcast() { - return model.listeners.fire; + return model.listeners.invoke(); } } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerObjectModel.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerObjectModel.java index d16a415f23..bf5ac32e17 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerObjectModel.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerObjectModel.java @@ -263,6 +263,6 @@ public class TestDebuggerObjectModel extends EmptyDebuggerObjectModel { } public DebuggerModelListener fire() { - return listeners.fire; + return listeners.invoke(); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java index 8e2c0dd29d..1214d2ac23 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java @@ -141,14 +141,14 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace protected Set viewports = new WeakHashCowSet<>(); protected ListenerSet directListeners = - new ListenerSet<>(DBTraceDirectChangeListener.class); + new ListenerSet<>(DBTraceDirectChangeListener.class, true); protected DBTraceVariableSnapProgramView programView; protected Set programViews = new WeakHashCowSet<>(); protected Set programViewsView = Collections.unmodifiableSet(programViews); protected Map fixedProgramViews = new WeakValueHashCowMap<>(); // NOTE: Can't pre-construct unmodifiableMap(fixedProgramViews), because values()' id changes protected ListenerSet viewListeners = - new ListenerSet<>(TraceProgramViewListener.class); + new ListenerSet<>(TraceProgramViewListener.class, true); public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer) throws IOException, LanguageNotFoundException { @@ -591,7 +591,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace super.fireEvent(ev); if (directListeners != null) { // Some events fire during construction - directListeners.fire.changed(ev); + directListeners.invoke().changed(ev); } } @@ -613,7 +613,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace return new DBTraceProgramView(this, snap, baseCompilerSpec); }); } - viewListeners.fire.viewCreated(view); + viewListeners.invoke().viewCreated(view); return view; } @@ -625,7 +625,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace view = new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec); programViews.add(view); } - viewListeners.fire.viewCreated(view); + viewListeners.invoke().viewCreated(view); return view; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java index ac8db646d0..0427a684ea 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java @@ -55,7 +55,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport { */ protected final List ordered = new ArrayList<>(); protected final MutableLifeSet spanSet = new DefaultLifeSet(); - protected final ListenerSet changeListeners = new ListenerSet<>(Runnable.class); + protected final ListenerSet changeListeners = new ListenerSet<>(Runnable.class, true); protected long snap = 0; @@ -224,7 +224,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport { } } assert !ordered.isEmpty(); - changeListeners.fire.run(); + changeListeners.invoke().run(); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RangeCursorTableHeaderRenderer.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RangeCursorTableHeaderRenderer.java index 1648b0b228..60b52ff3aa 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RangeCursorTableHeaderRenderer.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RangeCursorTableHeaderRenderer.java @@ -84,7 +84,7 @@ public class RangeCursorTableHeaderRenderer> double pos = span * (e.getX() - colX) / myViewCol.getWidth() + fullRangeDouble.min(); - listeners.fire.accept(pos); + listeners.invoke().accept(pos); } } @@ -105,7 +105,7 @@ public class RangeCursorTableHeaderRenderer> private int savedViewColumn; private final ForSeekMouseListener forSeekMouseListener = new ForSeekMouseListener(); - private final ListenerSet listeners = new ListenerSet<>(SeekListener.class); + private final ListenerSet listeners = new ListenerSet<>(SeekListener.class, true); public RangeCursorTableHeaderRenderer(N pos) { this.pos = pos; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/DefaultObservableCollection.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/DefaultObservableCollection.java index 4ff3b11143..da95c2cea2 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/DefaultObservableCollection.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/DefaultObservableCollection.java @@ -24,6 +24,7 @@ public class DefaultObservableCollection ent : changes.entrySet()) { switch (ent.getValue()) { case ADDED: - listeners.fire.elementAdded(ent.getKey()); + listeners.invoke().elementAdded(ent.getKey()); break; case REMOVED: - listeners.fire.elementRemoved(ent.getKey()); + listeners.invoke().elementRemoved(ent.getKey()); break; case MODIFIED: - listeners.fire.elementModified(ent.getKey()); + listeners.invoke().elementModified(ent.getKey()); break; } } @@ -115,7 +116,7 @@ public class DefaultObservableCollection wrapped, Class listenerClass) { this.wrapped = wrapped; - this.listeners = new ListenerSet<>(listenerClass); - this.l = this.listeners.fire; + this.listeners = new ListenerSet<>(listenerClass, true); + this.l = this.listeners.getProxy(); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/ListenerMap.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/ListenerMap.java deleted file mode 100644 index f28b381573..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/ListenerMap.java +++ /dev/null @@ -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 - * - *

- * 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. - * - *

- * 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 the type of keys - * @param

the interface of the proxy and multiplexed listeners - * @param the type of listeners - */ -public class ListenerMap { - private static final boolean DEBUG_INCEPTION = false; - - public static class ListenerEntry extends WeakReference { - 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 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. - * - *

- * 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. - * - *

- * This method is for testing. If listeners are involved in a test, then this should be called - * after that test. - * - *

- * 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 first 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 implements InvocationHandler { - protected final Class ext; - - public ListenerHandler(Class 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> listenersVolatile; - synchronized (lock) { - listenersVolatile = map.values(); - } - for (ListenerEntry 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

iface; - private final Executor executor; - private Map> 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, P> extFires = new HashMap<>(); - - /** - * Construct a new map whose proxy implements the given interface - * - *

- * The values in the map must implement the same interface. - * - *

- * 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

iface) { - this(iface, CALLING_THREAD); - } - - /** - * Construct a new map whose proxy implements the given interface - * - *

- * The values in the map must implement the same interface. - * - * @param iface the interface to multiplex - */ - public ListenerMap(Class

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> createMap() { - return new HashMap<>(); - } - - protected void notifyRemoved(ListenerEntry entry) { - Msg.warn(this, "Listener garbage collected before removal: " + entry.desc); - } - - @SuppressWarnings("unchecked") - public T fire(Class 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> newMap) { - for (Entry> 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> newMap = createMap(); - doPutAllInto(newMap); - ListenerEntry result = newMap.put(key, new ListenerEntry<>(val)); - map = newMap; - return result == null ? null : result.get(); - } - } - - public void putAll(ListenerMap that) { - synchronized (lock) { - Map> newMap = createMap(); - doPutAllInto(newMap); - that.doPutAllInto(newMap); - map = newMap; - } - } - - public V get(K key) { - ListenerEntry entry = map.get(key); - return entry == null ? null : entry.get(); - } - - public V remove(K key) { - synchronized (lock) { - if (!map.containsKey(key)) { - return null; - } - Map> newMap = createMap(); - doPutAllInto(newMap); - ListenerEntry result = newMap.remove(key); - map = newMap; - return result == null ? null : result.get(); - } - } - - public void clear() { - synchronized (lock) { - if (map.isEmpty()) { - return; - } - map = createMap(); - } - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/ListenerSet.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/ListenerSet.java deleted file mode 100644 index f5dce7a6d1..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/ListenerSet.java +++ /dev/null @@ -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 the type of multiplexed listeners - */ -public class ListenerSet { - public static final Executor CALLING_THREAD = ListenerMap.CALLING_THREAD; - private final ListenerMap 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 - * - *

- * 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 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 iface, Executor executor) { - map = new ListenerMap(iface, executor) { - @Override - protected Map> createMap() { - return ListenerSet.this.createMap(); - }; - }; - fire = map.fire; - } - - @Override - public String toString() { - return map.toString(); - } - - protected Map> createMap() { - // TODO: This doesn't prevent the automatic removal of an entry in the "immutable" map. - return new WeakHashMap<>(); - } - - public T fire(Class 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 c) { - map.putAll((ListenerMap) c.map); - } - - public boolean remove(E e) { - return map.remove(e) == e; - } - - public void clear() { - map.clear(); - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/PrivatelyQueuedListener.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/PrivatelyQueuedListener.java index f6a0615585..3fea52bb52 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/PrivatelyQueuedListener.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/datastruct/PrivatelyQueuedListener.java @@ -16,6 +16,7 @@ package ghidra.util.datastruct; import java.lang.reflect.*; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -28,6 +29,9 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory; */ public class PrivatelyQueuedListener

{ + private ListenerErrorHandler errorHandler = + DataStructureErrorHandlerFactory.createListenerErrorHandler(); + protected class ListenerHandler implements InvocationHandler { private static final Method OBJECT_HASHCODE; static { @@ -55,10 +59,10 @@ public class PrivatelyQueuedListener

{ } catch (InvocationTargetException e) { Throwable cause = e.getCause(); - ListenerMap.reportError(out, cause); + errorHandler.handleError(out, cause); } catch (Throwable e) { - ListenerMap.reportError(out, e); + errorHandler.handleError(out, e); } }); return null; // Assumes void return type @@ -75,7 +79,7 @@ public class PrivatelyQueuedListener

{ /** * Create a new privately-queued listener which will invoke the given "output" listener - * + * *

* Invoking the listener methods of {@link #in} will cause that invocation to be queued and * eventually delivered to the given output listener. Note, as a result, it is assumed all @@ -83,7 +87,7 @@ public class PrivatelyQueuedListener

{ * the invocation to complete, which defeats the purpose of the private queue. The invocations * on {@link #in} will always return {@code null}, which will cause an exception if the return * type is a different primitive. - * + * * @param iface the interface of the listener * @param executor the executor representing the processing queue * @param out the listener to receive the queued invocations @@ -97,17 +101,17 @@ public class PrivatelyQueuedListener

{ /** * Create a new single-threaded privately-queued listener - * - * @see {@link #PrivatelyQueuedListener(Class, Executor, Object)} + * * @param iface the interface of the listener * @param threadNamePattern a pattern for naming the single thread * @param out the listener to receive the queued invocations */ public PrivatelyQueuedListener(Class

iface, String threadNamePattern, P out) { - this(iface, - Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() - .namingPattern(threadNamePattern) - .build()), - out); + this(iface, Executors.newSingleThreadExecutor( + new BasicThreadFactory.Builder().namingPattern(threadNamePattern).build()), out); + } + + public void setErrorHandler(ListenerErrorHandler errorHandler) { + this.errorHandler = Objects.requireNonNull(errorHandler); } } diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/util/datastruct/ListenerMapTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/util/datastruct/ListenerMapTest.java deleted file mode 100644 index c642606873..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/util/datastruct/ListenerMapTest.java +++ /dev/null @@ -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 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 listeners = - new ListenerMap<>(DummyListener.class); - AtomicReference ar1 = new AtomicReference<>(); - listeners.put("Key1", ar1::set); - listeners.fire.event("EventA"); - assertEquals("EventA", ar1.get()); - AtomicReference ar2 = new AtomicReference<>(); - listeners.put("Key2", ar2::set); - listeners.fire.event("EventB"); - assertEquals("EventB", ar1.get()); - assertEquals("EventB", ar2.get()); - AtomicReference 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 listeners = - new ListenerMap<>(DummyListener.class, executor); - - Map> stalls = Map.ofEntries( - Map.entry("StallA", new CompletableFuture<>()), - Map.entry("StallB", new CompletableFuture<>()), - Map.entry("StallD", new CompletableFuture<>())); - AtomicReference 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 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 listeners = - new ListenerMap<>(DummyListener.class); - - AtomicReference ar1 = new AtomicReference<>(); - DummyListener d1 = e -> { - ar1.set(e); - throw new RuntimeException("It had better continue (1)"); - }; - listeners.put("Key1", d1); - - AtomicReference 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 listeners = - new ListenerMap<>(DummyListener.class); - - AtomicReference 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()); - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/util/datastruct/ListenerSetTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/util/datastruct/ListenerSetTest.java deleted file mode 100644 index 63334dfb7e..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/util/datastruct/ListenerSetTest.java +++ /dev/null @@ -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 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 listeners = new ListenerSet<>(DummyListener.class); - - AtomicReference ar1 = new AtomicReference<>(); - DummyListener d1 = e -> { - ar1.set(e); - throw new RuntimeException("It had better continue (1)"); - }; - listeners.add(d1); - - AtomicReference 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 listeners = new ListenerSet<>(DummyListener.class); - - AtomicReference 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()); - } -} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/ConcurrentTestExceptionHandler.java b/Ghidra/Framework/Generic/src/main/java/generic/test/ConcurrentTestExceptionHandler.java index 5d971df386..d24b765c0a 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/ConcurrentTestExceptionHandler.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/ConcurrentTestExceptionHandler.java @@ -22,7 +22,7 @@ import java.util.*; import org.apache.commons.lang3.StringUtils; import ghidra.util.Msg; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; /** * A class which handles exceptions that occur off of the main test thread. Exceptions can be @@ -33,7 +33,7 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler // Exception messages that we choose to ignore private static final String[] IGNORABLE_ERROR_MESSAGES = new String[] { "DerivedColor$UIResource cannot be cast to", // test machine timing issue - "FontUIResource cannot be cast to javax.swing.Painter", // test machine timing issue + "FontUIResource cannot be cast to javax.swing.Painter", // test machine timing issue }; private static final List throwables = @@ -41,13 +41,23 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler private static volatile boolean enabled = true; + /** + * Installs this exception handler as the default uncaught exception handler. See + * {@link Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)} + */ public static void registerHandler() { - SystemUtilities.runSwingLater(() -> { - // do this on the Swing thread + // Note: not sure why this is done on the Swing thread later. Seems like this could be done + // when this method is called, from any thread. + Swing.runLater(() -> { Thread.setDefaultUncaughtExceptionHandler(new ConcurrentTestExceptionHandler()); }); } + /** + * Tells this class to process the given throwable + * @param thread the thread that encountered the throwable + * @param t the throwable + */ public synchronized static void handle(Thread thread, Throwable t) { if (!enabled) { @@ -72,10 +82,10 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler /** * Some exceptions that happen off the test thread are not serious enough to fail the test. - * For example, some exceptions happen on the headless test server due more to - * environmental issues rather than real problems. This method is intended to ignore + * For example, some exceptions happen on the headless test server due more to + * environmental issues rather than real problems. This method is intended to ignore * these less-than-serious issues. - * + * * @param t the throwable to examine * @return true if it should be ignored */ @@ -89,26 +99,49 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler return StringUtils.containsAny(message, IGNORABLE_ERROR_MESSAGES); } + /** + * Clears all exceptions being tracked by this class + */ public synchronized static void clear() { throwables.clear(); } + /** + * Enables this class after a call to {@link #disable()} has been made + */ public synchronized static void enable() { enabled = true; } + /** + * Disables this class's tracking of exceptions. Clients use this method to have this class + * ignore expected exceptions. This is a bit course-grained, as it does not allow clients to + * ignore specific expected exceptions. + */ public synchronized static void disable() { enabled = false; } + /** + * Returns true if this class is enabled. When disabled this class does not track exceptions. + * @return true if enabled + */ public synchronized static boolean isEnabled() { return enabled; } + /** + * Returns all exceptions tracked by this class + * @return all exceptions tracked by this class + */ public static synchronized List getExceptions() { return new ArrayList<>(throwables); } + /** + * Returns true if this class has been given any exceptions to handle since last being cleared + * @return true if this class has been given any exceptions to handle since last being cleared + */ public static synchronized boolean hasException() { return !throwables.isEmpty(); } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/CopyOnReadWeakSet.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/CopyOnReadWeakSet.java index f068abc377..bee0bb4715 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/CopyOnReadWeakSet.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/CopyOnReadWeakSet.java @@ -38,14 +38,18 @@ class CopyOnReadWeakSet extends WeakSet { } @Override - public synchronized void add(T t) { + public synchronized boolean add(T t) { maybeWarnAboutAnonymousValue(t); + boolean contains = weakHashStorage.containsKey(t); weakHashStorage.put(t, null); + return !contains; } @Override - public synchronized void remove(T t) { + public synchronized boolean remove(Object t) { + boolean contains = weakHashStorage.containsKey(t); weakHashStorage.remove(t); + return contains; } @Override @@ -64,7 +68,7 @@ class CopyOnReadWeakSet extends WeakSet { } @Override - public synchronized boolean contains(T t) { + public synchronized boolean contains(Object t) { return weakHashStorage.containsKey(t); } @@ -88,4 +92,32 @@ class CopyOnReadWeakSet extends WeakSet { return createCopy().stream(); } + @Override + public synchronized boolean addAll(Collection c) { + boolean changed = false; + for (T t : c) { + changed |= add(t); + } + return changed; + } + + @Override + public synchronized boolean retainAll(Collection c) { + boolean changed = false; + Iterator 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); + } + } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/CopyOnWriteWeakSet.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/CopyOnWriteWeakSet.java index a86af1c6a9..8ab1eac6cc 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/CopyOnWriteWeakSet.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/CopyOnWriteWeakSet.java @@ -52,38 +52,23 @@ class CopyOnWriteWeakSet extends WeakSet { return IteratorUtils.unmodifiableIterator(weakHashStorage.keySet().iterator()); } - /** - * Adds all items to this set. - *

- * Note: calling this method will only result in one copy operation. If {@link #add(Object)} - * were called instead for each item of the iterator, then each call would copy this set. - * - * @param it the items - */ @Override - public synchronized void addAll(Iterable it) { - // only make one copy for the entire set of changes instead of for each change, as calling - // add() would do - WeakHashMap newStorage = new WeakHashMap<>(weakHashStorage); - for (T t : it) { - newStorage.put(t, null); - } - weakHashStorage = newStorage; - } - - @Override - public synchronized void add(T t) { + public synchronized boolean add(T t) { maybeWarnAboutAnonymousValue(t); WeakHashMap newStorage = new WeakHashMap<>(weakHashStorage); + boolean contains = newStorage.containsKey(t); newStorage.put(t, null); weakHashStorage = newStorage; + return !contains; } @Override - public synchronized void remove(T t) { + public synchronized boolean remove(Object t) { WeakHashMap newStorage = new WeakHashMap<>(weakHashStorage); + boolean contains = newStorage.containsKey(t); newStorage.remove(t); weakHashStorage = newStorage; + return contains; } @Override @@ -107,7 +92,7 @@ class CopyOnWriteWeakSet extends WeakSet { } @Override - public boolean contains(T t) { + public boolean contains(Object t) { return weakHashStorage.containsKey(t); } @@ -120,4 +105,48 @@ class CopyOnWriteWeakSet extends WeakSet { public String toString() { return values().toString(); } + + /** + * Adds all items to this set. + *

+ * 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 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 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 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 newStorage = new WeakHashMap<>(weakHashStorage); + boolean changed = newStorage.keySet().removeAll(c); + weakHashStorage = newStorage; + return changed; + } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/DataStructureErrorHandlerFactory.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/DataStructureErrorHandlerFactory.java new file mode 100644 index 0000000000..44913df4b1 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/DataStructureErrorHandlerFactory.java @@ -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); + } + } +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ListenerErrorHandler.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ListenerErrorHandler.java new file mode 100644 index 0000000000..38857a19da --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ListenerErrorHandler.java @@ -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); +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ListenerErrorHandlerFactory.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ListenerErrorHandlerFactory.java new file mode 100644 index 0000000000..bdf2dabe97 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ListenerErrorHandlerFactory.java @@ -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(); +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ListenerSet.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ListenerSet.java new file mode 100644 index 0000000000..b29336d4c5 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ListenerSet.java @@ -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: + *

    + *
  • 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. + *
  • + *
  • This class is thread safe, allowing adding and removing listeners while events are being + * fired. + *
  • + *
  • Weak or strong references may be used seamlessly by passing the correct constructor value. + *
  • + *
+ * + *

+ * Some restrictions: + *

    + *
  • 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. + *
  • + *
  • 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. + *
  • + *
  • The insertion order of listeners is not maintained, which means that event notification may + * take place in an arbitrary order. + *
  • + *
+ * + *

+ * An example use of this class to fire events could look like this: + *

+ *     ListenerSet<ActionListener> listeners = new ListenerSet(ActionListener.class);
+ *     ActionEvent event = new ActionEvent(this, 1, "Event");
+ *     listeners.invoke().actionPerformed(event);
+ * 
+ * + * @param the listener type + */ +public class ListenerSet { + + /** + * A proxy which passes invocations to each member of this set + */ + private final T proxy; + private final ThreadSafeListenerStorage 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 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); + } +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ThreadSafeListenerStorage.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ThreadSafeListenerStorage.java new file mode 100644 index 0000000000..a79c816b25 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ThreadSafeListenerStorage.java @@ -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. + *

+ * 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. + *

+ * 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 the storage type + */ +class ThreadSafeListenerStorage { + + // Creates a new set and adds all values from the optional set argument + private Factory, Set> factory; + private Set storage; + private AtomicInteger iteratorCount = new AtomicInteger(); + + ThreadSafeListenerStorage(boolean isWeak) { + this(createFactory(isWeak)); + } + + ThreadSafeListenerStorage(Factory, Set> factory) { + this.factory = factory; + this.storage = factory.get(null); + } + + void forEach(Consumer c) { + + Set toIterate = getSet(); + + try { + for (T t : toIterate) { + c.accept(t); + } + } + finally { + iteratorCount.decrementAndGet(); + } + } + + private synchronized Set 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 Factory, Set> createFactory(boolean isWeak) { + if (isWeak) { + return new WeakSetFactory(); + } + return new StrongSetFactory(); + } + + private static class WeakSetFactory implements Factory, Set> { + + @Override + public Set get(Set set) { + Set newSet = new ThreadUnsafeWeakSet<>(); + if (set != null) { + newSet.addAll(set); + } + return newSet; + } + + } + + private static class StrongSetFactory implements Factory, Set> { + @Override + public Set get(Set set) { + Set newSet = new HashSet<>(); + if (set != null) { + newSet.addAll(set); + } + return newSet; + } + } + +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ThreadUnsafeWeakSet.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ThreadUnsafeWeakSet.java index 01f46c2896..cd3f9ad159 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ThreadUnsafeWeakSet.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ThreadUnsafeWeakSet.java @@ -26,14 +26,18 @@ class ThreadUnsafeWeakSet extends WeakSet { } @Override - public void add(T t) { + public boolean add(T t) { maybeWarnAboutAnonymousValue(t); + boolean contains = weakHashStorage.containsKey(t); weakHashStorage.put(t, null); + return !contains; } @Override - public void remove(T t) { + public boolean remove(Object t) { + boolean contains = weakHashStorage.containsKey(t); weakHashStorage.remove(t); + return contains; } @Override @@ -62,7 +66,7 @@ class ThreadUnsafeWeakSet extends WeakSet { } @Override - public boolean contains(T t) { + public boolean contains(Object t) { return weakHashStorage.containsKey(t); } @@ -76,4 +80,31 @@ class ThreadUnsafeWeakSet extends WeakSet { return values().stream(); } + @Override + public boolean addAll(Collection c) { + boolean changed = false; + for (T t : c) { + changed |= add(t); + } + return changed; + } + + @Override + public boolean retainAll(Collection c) { + boolean changed = false; + Iterator 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); + } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakDataStructureFactory.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakDataStructureFactory.java index 7731cdab98..591d5e427e 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakDataStructureFactory.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakDataStructureFactory.java @@ -36,16 +36,6 @@ public class WeakDataStructureFactory { return new ThreadUnsafeWeakSet<>(); } - /** - * Use to signal that the returned weak set is not thread safe and must be protected accordingly - * when used in a multi-threaded environment. - * - * @return a new WeakSet - */ - public static WeakSet createThreadUnsafeWeakSet() { - return new ThreadUnsafeWeakSet<>(); - } - /** * Use when mutations outweigh iterations. * diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakSet.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakSet.java index 2e54d74cec..0a94423a67 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakSet.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakSet.java @@ -17,14 +17,13 @@ package ghidra.util.datastruct; import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.util.Collection; -import java.util.WeakHashMap; +import java.util.*; import java.util.stream.Stream; import ghidra.util.Msg; import ghidra.util.SystemUtilities; -public abstract class WeakSet implements Iterable { +public abstract class WeakSet implements Set { private static final boolean WARN_ON_ANONYMOUS_VALUE = SystemUtilities.isInDevelopmentMode() || SystemUtilities.isInTestingMode(); @@ -78,51 +77,47 @@ public abstract class WeakSet implements Iterable { // Interface Methods //================================================================================================== - /** - * Adds all items to this set - * @param it the items - */ - public void addAll(Iterable it) { - for (T t : it) { - add(t); - } - } - /** * Add the given object to the set * @param t the object to add */ - public abstract void add(T t); + @Override + public abstract boolean add(T t); /** * Remove the given object from the data structure * @param t the object to remove * */ - public abstract void remove(T t); + @Override + public abstract boolean remove(Object t); /** * Returns true if the given object is in this data structure * @param t the object * @return true if the given object is in this data structure */ - public abstract boolean contains(T t); + @Override + public abstract boolean contains(Object t); /** * Remove all elements from this data structure */ + @Override public abstract void clear(); /** * Return the number of objects contained within this data structure * @return the size */ + @Override public abstract int size(); /** * Return whether this data structure is empty * @return whether this data structure is empty */ + @Override public abstract boolean isEmpty(); /** @@ -132,9 +127,36 @@ public abstract class WeakSet implements Iterable { */ public abstract Collection values(); + @Override + public Object[] toArray() { + return weakHashStorage.keySet().toArray(); + } + + // is hiding the class declaration; it is needed to satisfy the interface + @SuppressWarnings("hiding") + @Override + public 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 c); + + @Override + public abstract boolean retainAll(Collection c); + + @Override + public abstract boolean removeAll(Collection c); + /** * Returns a stream of the values of this collection. * @return a stream of the values of this collection. */ + @Override public abstract Stream stream(); } diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/ListenerSetTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/ListenerSetTest.java new file mode 100644 index 0000000000..aab122b7ac --- /dev/null +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/ListenerSetTest.java @@ -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 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 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 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 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 listeners = new ListenerSet<>(DummyListener.class, true); + SpyErrorHandler spyErrorHandler = new SpyErrorHandler(); + listeners.setErrorHandler(spyErrorHandler); + + int n = 5; + List 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 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 listeners = new ListenerSet<>(DummyListener.class, true); + SpyErrorHandler spyErrorHandler = new SpyErrorHandler(); + listeners.setErrorHandler(spyErrorHandler); + + int n = 5; + List 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 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 createLatchedListeners(int n) { + List 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 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 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; + } + } + +} diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/TestDataStructureErrorHandlerInstaller.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/TestDataStructureErrorHandlerInstaller.java new file mode 100644 index 0000000000..aa64f403c9 --- /dev/null +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/TestDataStructureErrorHandlerInstaller.java @@ -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. + *

+ * 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); + } + } +} diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/WeakSetTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/WeakSetTest.java index d6d4011749..76089b5c51 100644 --- a/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/WeakSetTest.java +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/WeakSetTest.java @@ -15,64 +15,22 @@ */ package ghidra.util.datastruct; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.*; -import org.junit.*; +import org.junit.Test; import generic.test.AbstractGenericTest; /** * Tests the {@link WeakSet} class. - * - * + * + * * @since Tracker Id 522 */ public class WeakSetTest extends AbstractGenericTest { - /** - * Creates an instance of this test class with the provided test name. - * - */ - public WeakSetTest() { - super(); - } - - @Before - public void setUp() throws Exception { - - } - - @After - public void tearDown() throws Exception { - - } - -// /* -// * Test method for 'ghidra.util.WeakSet.WeakSet(Class)' -// */ -// public void testConstructor() { -// // valid instantiation -// new WeakSet(); -// -// // invalid parameter test -// try { -// new WeakSet( null ); -// -// Assert.fail( "Passing null to the WeakSet's constructor did not trigger " + -// "a NullPointerException." ); -// } catch ( NullPointerException npe ) { -// // good, expected -// } -// } - - /* - * Test method for 'ghidra.util.WeakSet.add(Object)' and - * 'ghidra.util.WeakSet.add(Object)' - */ @Test public void testAddAndRemove() { WeakSet weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet(); @@ -98,11 +56,6 @@ public class WeakSetTest extends AbstractGenericTest { } } - /* - * Test method for 'ghidra.util.WeakSet.clear()', - * 'ghidra.util.WeakSet.clear()' and - * 'ghidra.util.WeakSet.isEmpty()' - */ @Test public void testClear() { WeakSet weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet(); @@ -125,35 +78,32 @@ public class WeakSetTest extends AbstractGenericTest { weakSet.isEmpty()); } -// /* -// * Test method for 'ghidra.util.WeakSet.toArray()' -// */ -// public void testToArray() { -// WeakSet weakSet = new WeakSet(); -// -// String[] values = { "one", "two", "three" }; -// -// // test add -// for (int i = 0; i < values.length; i++) { -// weakSet.add( values[i] ); -// } -// -// assertTrue( "The weak set does not contain the correct number of " + -// "elements after calling add().", (values.length==weakSet.size()) ); -// -// Object[] valuesArray = weakSet.toArray(); -// -// // check the array against our values -// assertTrue( "The weak set returned a values array that is not the " + -// "size as the number of values passed in.", -// (valuesArray.length==values.length) ); -// -// List valuesList = new ArrayList( Arrays.asList( values ) ); -// for (int i = 0; i < valuesArray.length; i++) { -// assertTrue( "An element returned from the weak set was not " + -// "passed to the set.", valuesList.contains( valuesArray[i] ) ); -// } -// } + @Test + public void testToArray() { + WeakSet weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet(); + + String[] values = { "one", "two", "three" }; + + // test add + for (String value : values) { + weakSet.add(value); + } + + assertTrue("The weak set does not contain the correct number of " + + "elements after calling add().", (values.length == weakSet.size())); + + Object[] valuesArray = weakSet.toArray(); + + // check the array against our values + assertTrue("The weak set returned a values array that is not the " + + "size as the number of values passed in.", (valuesArray.length == values.length)); + + List valuesList = new ArrayList<>(Arrays.asList(values)); + for (Object element : valuesArray) { + assertTrue("An element returned from the weak set was not " + "passed to the set.", + valuesList.contains(element)); + } + } /* * Test method for 'ghidra.util.WeakSet.getListeners()' @@ -187,66 +137,68 @@ public class WeakSetTest extends AbstractGenericTest { } // Commented out because it was slow +// @Test // public void testReferencesRemovedAfterCollection() { // // create some references and hold on to them to make sure that // // they are not collected and stay in the set // WeakSet weakSet = WeakDataStructureFactory.createCopyOnWriteWeakSet(); // -// ActionListener[] values = -// new ActionListener[] { new ActionListenerAdapter(), new ActionListenerAdapter(), -// new ActionListenerAdapter(), new ActionListenerAdapter() }; +// ActionListener[] values = new ActionListener[] { new ActionListenerAdapter(), +// new ActionListenerAdapter(), new ActionListenerAdapter(), new ActionListenerAdapter() }; // // // test add -// for (int i = 0; i < values.length; i++) { -// weakSet.add(values[i]); +// for (ActionListener value : values) { +// weakSet.add(value); // } // -// assertTrue("The weak set does not contain the correct number of " -// + "elements after calling add().", (values.length == weakSet.size())); +// assertTrue("The weak set does not contain the correct number of " + +// "elements after calling add().", (values.length == weakSet.size())); // // // now release *all* those references +// for (int i = 0; i < values.length; i++) { +// values[i] = null; +// } // values = null; // -// // force garbage collection +// // force garbage collection // forceGarbageCollection(); // // // make sure that the unreferenced objects are removed from the set -// assertTrue("The elements added to the weak set were not removed " -// + "when they were no longer referenced.", (0 == weakSet.size())); +// assertTrue("The elements added to the weak set were not removed " + +// "when they were no longer referenced.", (0 == weakSet.size())); // // // now try the test again while only deleting some of the values -// values = -// new ActionListener[] { new ActionListenerAdapter(), new ActionListenerAdapter(), -// new ActionListenerAdapter(), new ActionListenerAdapter() }; +// values = new ActionListener[] { new ActionListenerAdapter(), new ActionListenerAdapter(), +// new ActionListenerAdapter(), new ActionListenerAdapter() }; // -// for (int i = 0; i < values.length; i++) { -// weakSet.add(values[i]); +// for (ActionListener value : values) { +// weakSet.add(value); // } // -// assertTrue("The weak set does not contain the correct number of " -// + "elements after calling add().", (values.length == weakSet.size())); +// assertTrue("The weak set does not contain the correct number of " + +// "elements after calling add().", (values.length == weakSet.size())); // // // null out some values // values[0] = null; // values[2] = null; // -// // force garbage collection +// // force garbage collection // forceGarbageCollection(); // // // make sure that the unreferenced objects are removed from the set -// assertTrue("The elements added to the weak set were not removed " -// + "when they were no longer referenced.", (2 == weakSet.size())); +// assertTrue("The elements added to the weak set were not removed " + +// "when they were no longer referenced.", (2 == weakSet.size())); +// } +// +// class ActionListenerAdapter implements ActionListener { +// @Override +// public void actionPerformed(ActionEvent event) { +// // stub implementation +// } // } // - class ActionListenerAdapter implements ActionListener { - @Override - public void actionPerformed(ActionEvent event) { - // stub implementation - } - } - // private void forceGarbageCollection() { -// waitForSwing(); +// // System.gc(); // System.gc(); // try {