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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -174,12 +174,12 @@ public interface DbgModelTargetBreakpointSpec extends //
/**
* Update the enabled field
*
*
* <p>
* This does not actually toggle the breakpoint. It just updates the field and calls the proper
* listeners. To actually toggle the breakpoint, use {@link #toggle(boolean)} instead, which if
* effective, should eventually cause this method to be called.
*
*
* @param enabled true if enabled, false if disabled
* @param reason a description of the cause (not really used, yet)
*/
@ -207,8 +207,8 @@ public interface DbgModelTargetBreakpointSpec extends //
public default void breakpointHit() {
DbgModelTargetThread targetThread =
getParentProcess().getThreads().getTargetThread(getManager().getEventThread());
getActions().fire.breakpointHit((DbgModelTargetBreakpointSpec) getProxy(), targetThread,
null, this);
getActions().invoke()
.breakpointHit((DbgModelTargetBreakpointSpec) getProxy(), targetThread, null, this);
}
}

View file

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

View file

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

View file

@ -15,7 +15,8 @@
*/
package agent.dbgeng.model.impl;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import agent.dbgeng.manager.breakpoint.DbgBreakpointInfo;
@ -27,33 +28,21 @@ import ghidra.dbg.target.TargetBreakpointSpec;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
import ghidra.dbg.util.PathUtils;
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
import ghidra.util.datastruct.ListenerSet;
@TargetObjectSchemaInfo(
name = "BreakpointSpec",
attributes = { //
@TargetAttributeType( //
@TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = { //
@TargetAttributeType( //
name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, //
type = DbgModelTargetBreakpointContainerImpl.class), //
@TargetAttributeType( //
@TargetAttributeType( //
name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, //
type = DbgModelTargetBreakpointSpecImpl.class), //
@TargetAttributeType(
name = DbgModelTargetBreakpointSpecImpl.BPT_TYPE_ATTRIBUTE_NAME,
type = String.class), //
@TargetAttributeType(
name = DbgModelTargetBreakpointSpecImpl.BPT_DISP_ATTRIBUTE_NAME,
type = String.class), //
@TargetAttributeType(
name = DbgModelTargetBreakpointSpecImpl.BPT_PENDING_ATTRIBUTE_NAME,
type = String.class), //
@TargetAttributeType(
name = DbgModelTargetBreakpointSpecImpl.BPT_TIMES_ATTRIBUTE_NAME,
type = Integer.class), //
@TargetAttributeType(type = Void.class) //
},
canonicalContainer = true)
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_TYPE_ATTRIBUTE_NAME, type = String.class), //
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_DISP_ATTRIBUTE_NAME, type = String.class), //
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_PENDING_ATTRIBUTE_NAME, type = String.class), //
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_TIMES_ATTRIBUTE_NAME, type = Integer.class), //
@TargetAttributeType(type = Void.class) //
}, canonicalContainer = true)
public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetBreakpointSpec {
@ -69,28 +58,21 @@ public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl
protected boolean enabled;
public void changeAttributeSet(String reason) {
this.changeAttributes(List.of(), List.of(), Map.of(
DISPLAY_ATTRIBUTE_NAME, "[" + info.getNumber() + "] " + info.getExpression(),
RANGE_ATTRIBUTE_NAME, doGetRange(),
SPEC_ATTRIBUTE_NAME, this,
EXPRESSION_ATTRIBUTE_NAME, info.getExpression(),
KINDS_ATTRIBUTE_NAME, getKinds(),
this.changeAttributes(List.of(), List.of(),
Map.of(DISPLAY_ATTRIBUTE_NAME, "[" + info.getNumber() + "] " + info.getExpression(),
RANGE_ATTRIBUTE_NAME, doGetRange(), SPEC_ATTRIBUTE_NAME, this,
EXPRESSION_ATTRIBUTE_NAME, info.getExpression(), KINDS_ATTRIBUTE_NAME, getKinds(),
BPT_TYPE_ATTRIBUTE_NAME, info.getType().name(),
BPT_DISP_ATTRIBUTE_NAME, info.getDisp().name(),
BPT_PENDING_ATTRIBUTE_NAME, info.getPending(),
BPT_TIMES_ATTRIBUTE_NAME, info.getTimes()),
BPT_TYPE_ATTRIBUTE_NAME, info.getType().name(), BPT_DISP_ATTRIBUTE_NAME,
info.getDisp().name(), BPT_PENDING_ATTRIBUTE_NAME, info.getPending(),
BPT_TIMES_ATTRIBUTE_NAME, info.getTimes()),
reason);
}
// Use strong references on actions
// The values may be weak, but the keys, which are the same objects, are strong
private final ListenerSet<TargetBreakpointAction> actions =
new ListenerSet<>(TargetBreakpointAction.class) {
// Use strong references on actions
// The values may be weak, but the keys, which are the same objects, are strong
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
return new LinkedHashMap<>();
}
};
new ListenerSet<>(TargetBreakpointAction.class, false);
public DbgModelTargetBreakpointSpecImpl(DbgModelTargetBreakpointContainer breakpoints,
DbgBreakpointInfo info) {
@ -128,11 +110,11 @@ public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl
/**
* Update the enabled field
*
*
* This does not actually toggle the breakpoint. It just updates the field and calls the proper
* listeners. To actually toggle the breakpoint, use {@link #toggle(boolean)} instead, which if
* effective, should eventually cause this method to be called.
*
*
* @param enabled true if enabled, false if disabled
* @param reason a description of the cause (not really used, yet)
*/

View file

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

View file

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

View file

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

View file

@ -31,7 +31,6 @@ import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointAction;
import ghidra.dbg.util.PathUtils;
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
import ghidra.util.datastruct.ListenerSet;
public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl implements //
@ -69,79 +68,81 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
}
}
protected static Class<? extends DbgModelTargetObject> lookupWrapperType(String type, String parentName) {
protected static Class<? extends DbgModelTargetObject> lookupWrapperType(String type,
String parentName) {
switch (type) {
case "Available":
return DbgModelTargetAvailableContainer.class;
case "Sessions":
return DbgModelTargetSessionContainer.class;
case "Processes":
return DbgModelTargetProcessContainer.class;
case "Threads":
return DbgModelTargetThreadContainer.class;
case "Modules":
return DbgModelTargetModuleContainer.class;
case "Frames":
return DbgModelTargetStack.class;
case "Registers":
return DbgModelTargetRegisterContainer.class;
case "Attributes":
return DbgModelTargetSessionAttributes.class;
case "Breakpoints":
return DbgModelTargetBreakpointContainer.class;
case "cursession":
return DbgModelTargetSession.class;
case "curprocess":
return DbgModelTargetProcess.class;
case "curthread":
return DbgModelTargetThread.class;
case "curframe":
return DbgModelTargetStackFrame.class;
case "User":
return DbgModelTargetRegisterBank.class;
case "TTD":
return DbgModelTargetTTD.class;
case "Debug":
return DbgModelTargetDebugContainer.class;
case "Available":
return DbgModelTargetAvailableContainer.class;
case "Sessions":
return DbgModelTargetSessionContainer.class;
case "Processes":
return DbgModelTargetProcessContainer.class;
case "Threads":
return DbgModelTargetThreadContainer.class;
case "Modules":
return DbgModelTargetModuleContainer.class;
case "Frames":
return DbgModelTargetStack.class;
case "Registers":
return DbgModelTargetRegisterContainer.class;
case "Attributes":
return DbgModelTargetSessionAttributes.class;
case "Breakpoints":
return DbgModelTargetBreakpointContainer.class;
case "cursession":
return DbgModelTargetSession.class;
case "curprocess":
return DbgModelTargetProcess.class;
case "curthread":
return DbgModelTargetThread.class;
case "curframe":
return DbgModelTargetStackFrame.class;
case "User":
return DbgModelTargetRegisterBank.class;
case "TTD":
return DbgModelTargetTTD.class;
case "Debug":
return DbgModelTargetDebugContainer.class;
}
if (parentName != null) {
switch (parentName) {
case "Available":
return DbgModelTargetAvailable.class;
case "Sessions":
return DbgModelTargetSession.class;
case "Processes":
return DbgModelTargetProcess.class;
case "Threads":
return DbgModelTargetThread.class;
case "Modules":
return DbgModelTargetModule.class;
case "Frames":
return DbgModelTargetStackFrame.class;
case "Breakpoints":
return DbgModelTargetBreakpointSpec.class;
// case "Registers":
// return DbgModelTargetRegisterBank.class;
case "FloatingPoint":
case "Kernel":
case "SIMD":
case "VFP":
case "User":
return DbgModelTargetRegister.class;
case "Available":
return DbgModelTargetAvailable.class;
case "Sessions":
return DbgModelTargetSession.class;
case "Processes":
return DbgModelTargetProcess.class;
case "Threads":
return DbgModelTargetThread.class;
case "Modules":
return DbgModelTargetModule.class;
case "Frames":
return DbgModelTargetStackFrame.class;
case "Breakpoints":
return DbgModelTargetBreakpointSpec.class;
// case "Registers":
// return DbgModelTargetRegisterBank.class;
case "FloatingPoint":
case "Kernel":
case "SIMD":
case "VFP":
case "User":
return DbgModelTargetRegister.class;
}
}
return null;
}
public static DbgModelTargetObject makeProxy(DbgModel2Impl model, DbgModelTargetObject parent, String key,
ModelObject object) {
public static DbgModelTargetObject makeProxy(DbgModel2Impl model, DbgModelTargetObject parent,
String key, ModelObject object) {
List<Class<? extends TargetObject>> mixins = new ArrayList<>();
String lkey = key;
String pname = parent.getName();
if (object.getKind().equals(ModelObjectKind.OBJECT_METHOD)) {
mixins.add(DbgModelTargetMethod.class);
} else {
}
else {
Class<? extends DbgModelTargetObject> mixin = lookupWrapperType(lkey, pname);
if (mixin != null) {
mixins.add(mixin);
@ -161,12 +162,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
private boolean breakpointEnabled;
private final ListenerSet<TargetBreakpointAction> breakpointActions =
new ListenerSet<>(TargetBreakpointAction.class) {
// Use strong references on actions
protected Map<TargetBreakpointAction, ListenerEntry<? extends TargetBreakpointAction>> createMap() {
return new LinkedHashMap<>();
}
};
new ListenerSet<>(TargetBreakpointAction.class, false);
// Extending DefaultTargetObject may spare you from listeners, elements, and
// attributes
@ -175,8 +171,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
// any other fields you need to support your impl
public DelegateDbgModel2TargetObject(DbgModel2Impl model, DbgModelTargetObject parent, String key,
ModelObject modelObject, List<Class<? extends TargetObject>> mixins) {
public DelegateDbgModel2TargetObject(DbgModel2Impl model, DbgModelTargetObject parent,
String key, ModelObject modelObject, List<Class<? extends TargetObject>> mixins) {
super(model, mixins, model, parent.getProxy(), key, getHintForObject(modelObject));
// System.err.println(this);
this.state = new ProxyState(model, modelObject);
@ -198,8 +194,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
if (mixin != null) {
mixins.add(mixin);
}
DelegateDbgModel2TargetObject delegate = new DelegateDbgModel2TargetObject(getModel(), p, key, modelObject,
mixins);
DelegateDbgModel2TargetObject delegate =
new DelegateDbgModel2TargetObject(getModel(), p, key, modelObject, mixins);
return delegate;
}
@ -226,41 +222,41 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
protected void checkExited(DbgState state, DbgCause cause) {
TargetExecutionState exec = TargetExecutionState.INACTIVE;
switch (state) {
case NOT_STARTED: {
exec = TargetExecutionState.INACTIVE;
break;
}
case STARTING: {
exec = TargetExecutionState.ALIVE;
break;
}
case RUNNING: {
exec = TargetExecutionState.RUNNING;
resetModified();
onRunning();
break;
}
case STOPPED: {
exec = TargetExecutionState.STOPPED;
onStopped();
break;
}
case EXIT: {
exec = TargetExecutionState.TERMINATED;
onExit();
break;
}
case SESSION_EXIT: {
getModel().close();
return;
}
case NOT_STARTED: {
exec = TargetExecutionState.INACTIVE;
break;
}
case STARTING: {
exec = TargetExecutionState.ALIVE;
break;
}
case RUNNING: {
exec = TargetExecutionState.RUNNING;
resetModified();
onRunning();
break;
}
case STOPPED: {
exec = TargetExecutionState.STOPPED;
onStopped();
break;
}
case EXIT: {
exec = TargetExecutionState.TERMINATED;
onExit();
break;
}
case SESSION_EXIT: {
getModel().close();
return;
}
}
if (proxy instanceof TargetExecutionStateful) {
if (proxy instanceof DbgModelTargetSession) {
if (state != DbgState.EXIT) {
setExecutionState(exec, "Refreshed");
}
}
}
else {
TargetExecutionState previous = this.getExecutionState();
if (!previous.equals(TargetExecutionState.INACTIVE)) {
@ -368,7 +364,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
}
if (proxy instanceof TargetAccessConditioned) {
changeAttributes(List.of(), List.of(), Map.of( //
TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME, accessible //
TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME, accessible //
), "Accessibility changed");
}
}
@ -420,16 +416,18 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
List<DelegateDbgModel2TargetObject> delegates = new ArrayList<>();
TargetObject stack = (TargetObject) getCachedAttribute("Stack");
if (stack != null) {
DbgModelTargetStack frames = (DbgModelTargetStack) stack.getCachedAttribute("Frames");
DbgModelTargetStack frames =
(DbgModelTargetStack) stack.getCachedAttribute("Frames");
delegates.add((DelegateDbgModel2TargetObject) frames.getDelegate());
}
DbgModelTargetRegisterContainer container = (DbgModelTargetRegisterContainer) getCachedAttribute(
"Registers");
DbgModelTargetRegisterContainer container =
(DbgModelTargetRegisterContainer) getCachedAttribute("Registers");
if (container == null) {
return;
}
delegates.add((DelegateDbgModel2TargetObject) container.getDelegate());
DbgModelTargetRegisterBank bank = (DbgModelTargetRegisterBank) container.getCachedAttribute("User");
DbgModelTargetRegisterBank bank =
(DbgModelTargetRegisterBank) container.getCachedAttribute("User");
if (bank == null) {
return;
}
@ -457,7 +455,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
for (TargetObject obj : getCachedElements().values()) {
if (obj instanceof TargetStackFrame) {
DbgModelTargetObject frame = (DbgModelTargetObject) obj;
DelegateDbgModel2TargetObject delegate = (DelegateDbgModel2TargetObject) frame.getDelegate();
DelegateDbgModel2TargetObject delegate =
(DelegateDbgModel2TargetObject) frame.getDelegate();
delegate.threadStateChangedSpecific(state, reason);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -94,7 +94,7 @@ public class LldbManagerImpl implements LldbManager {
private final HandlerMap<LldbEvent<?>, Void, DebugStatus> handlerMap = new HandlerMap<>();
private final Map<Class<?>, DebugStatus> statusMap = new LinkedHashMap<>();
private final ListenerSet<LldbEventsListener> listenersEvent =
new ListenerSet<>(LldbEventsListener.class);
new ListenerSet<>(LldbEventsListener.class, true);
private SBEvent currentEvent;
private SBTarget currentSession;
@ -116,7 +116,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Use {@link SBThreadImpl#remove()} instead
*
*
* @param id the thread ID to remove
*/
public void removeThread(String processId, String id) {
@ -136,8 +136,9 @@ public class LldbManagerImpl implements LldbManager {
public void addThreadIfAbsent(SBProcess process, SBThread thread) {
synchronized (threads) {
if (!process.IsValid())
if (!process.IsValid()) {
return;
}
Map<String, SBThread> map = threads.get(DebugClient.getId(process));
if (map == null) {
map = new HashMap<>();
@ -165,7 +166,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Use {@link SBProcessImpl#remove(LldbCause)} instead
*
*
* @param id the process ID to remove
* @param cause the cause of removal
*/
@ -188,18 +189,10 @@ public class LldbManagerImpl implements LldbManager {
for (String tid : toRemove) {
removeThread(processId, tid);
}
getEventListeners().fire.processRemoved(id, cause);
getEventListeners().invoke().processRemoved(id, cause);
}
}
/**
* Update the selected process
*
* @param process the process that now has focus
* @param cause the cause of the focus change
* @param fire signal listeners
* @return success status
*/
@Override
public SBProcess getProcess(SBTarget session, String id) {
synchronized (processes) {
@ -214,8 +207,9 @@ public class LldbManagerImpl implements LldbManager {
public void addProcessIfAbsent(SBTarget session, SBProcess process) {
synchronized (processes) {
if (!session.IsValid())
if (!session.IsValid()) {
return;
}
String sessionId = DebugClient.getId(session);
Map<String, SBProcess> map = processes.get(sessionId);
if (map == null) {
@ -242,7 +236,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Use {@link SBTargetImpl#remove(LldbCause)} instead
*
*
* @param id the session ID to remove
* @param cause the cause of removal
*/
@ -252,7 +246,7 @@ public class LldbManagerImpl implements LldbManager {
if (sessions.remove(id) == null) {
throw new IllegalArgumentException("There is no session with id " + id);
}
getEventListeners().fire.sessionRemoved(id, cause);
getEventListeners().invoke().sessionRemoved(id, cause);
}
}
@ -290,7 +284,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Use {@link SBModule#remove()} instead
*
*
* @param id the module name to remove
*/
public void removeModule(SBTarget session, String id) {
@ -310,8 +304,9 @@ public class LldbManagerImpl implements LldbManager {
public void addModuleIfAbsent(SBTarget session, SBModule module) {
synchronized (modules) {
if (!session.IsValid())
if (!session.IsValid()) {
return;
}
String sessionId = DebugClient.getId(session);
Map<String, SBModule> map = modules.get(sessionId);
if (map == null) {
@ -347,8 +342,9 @@ public class LldbManagerImpl implements LldbManager {
public void addBreakpointIfAbsent(SBTarget session, Object bpt) {
synchronized (breakpoints) {
if (!session.IsValid())
if (!session.IsValid()) {
return;
}
String sessionId = DebugClient.getId(session);
Map<String, Object> map = breakpoints.get(sessionId);
if (map == null) {
@ -484,13 +480,11 @@ public class LldbManagerImpl implements LldbManager {
state.set(null, Causes.UNCLAIMED);
boolean create = true;
if (args.length == 0) {
executor =
new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
executor = new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
}
else {
// TODO - process args
executor =
new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
executor = new LldbClientThreadExecutor(() -> DebugClient.debugCreate().createClient());
create = false;
}
executor.setManager(this);
@ -540,7 +534,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Schedule a command for execution
*
*
* @param cmd the command to execute
* @return the pending command, which acts as a future for later completion
*/
@ -741,16 +735,14 @@ public class LldbManagerImpl implements LldbManager {
@Override
public void updateState(SBProcess process) {
currentProcess = eventProcess = process;
if (currentSession == null ||
!currentSession.IsValid() ||
if (currentSession == null || !currentSession.IsValid() ||
!currentSession.equals(process.GetTarget())) {
SBTarget candidateSession = currentProcess.GetTarget();
if (candidateSession != null && candidateSession.IsValid()) {
currentSession = eventSession = candidateSession;
}
}
if (currentThread == null ||
!currentThread.IsValid() ||
if (currentThread == null || !currentThread.IsValid() ||
!currentThread.equals(process.GetSelectedThread())) {
SBThread candidateThread = currentProcess.GetSelectedThread();
if (candidateThread != null && candidateThread.IsValid()) {
@ -764,7 +756,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Default handler for events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -775,7 +767,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -785,7 +777,7 @@ public class LldbManagerImpl implements LldbManager {
for (int i = 0; i < currentSession.GetNumBreakpoints(); i++) {
SBBreakpoint bpt = currentSession.GetBreakpointAtIndex(i);
if (bpt.IsValid() && (bpt.GetID() == id.intValue())) {
getEventListeners().fire.breakpointHit(bpt, evt.getCause());
getEventListeners().invoke().breakpointHit(bpt, evt.getCause());
}
}
return statusMap.get(evt.getClass());
@ -793,7 +785,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -801,7 +793,7 @@ public class LldbManagerImpl implements LldbManager {
protected DebugStatus processException(LldbExceptionEvent evt, Void v) {
/*
Integer eventId = updateState(evt);
DebugExceptionRecord64 info = evt.getInfo();
String key = Integer.toHexString(info.code);
if (statusByNameMap.containsKey(key)) {
@ -813,7 +805,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -824,72 +816,72 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for thread created events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
*/
protected DebugStatus processThreadCreated(LldbThreadCreatedEvent evt, Void v) {
SBThread thread = evt.getInfo().thread;
getEventListeners().fire.threadCreated(thread, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
getEventListeners().invoke().threadCreated(thread, LldbCause.Causes.UNCLAIMED);
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for thread created events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
*/
protected DebugStatus processThreadReplaced(LldbThreadReplacedEvent evt, Void v) {
SBThread thread = evt.getInfo().thread;
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for thread exited events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
*/
protected DebugStatus processThreadExited(LldbThreadExitedEvent evt, Void v) {
getEventListeners().fire.threadExited(eventThread, eventProcess, evt.getCause());
getEventListeners().invoke().threadExited(eventThread, eventProcess, evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for thread selected events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
*/
protected DebugStatus processThreadSelected(LldbThreadSelectedEvent evt, Void v) {
currentThread = evt.getThread();
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause());
getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for frame selected events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
*/
protected DebugStatus processFrameSelected(LldbSelectedFrameChangedEvent evt, Void v) {
currentThread = evt.getThread();
getEventListeners().fire.threadSelected(currentThread, evt.getFrame(), evt.getCause());
getEventListeners().invoke().threadSelected(currentThread, evt.getFrame(), evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for process created events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -897,17 +889,17 @@ public class LldbManagerImpl implements LldbManager {
protected DebugStatus processProcessCreated(LldbProcessCreatedEvent evt, Void v) {
DebugProcessInfo info = evt.getInfo();
SBProcess proc = info.process;
getEventListeners().fire.processAdded(proc, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.processSelected(proc, evt.getCause());
getEventListeners().invoke().processAdded(proc, LldbCause.Causes.UNCLAIMED);
getEventListeners().invoke().processSelected(proc, evt.getCause());
SBThread thread = proc.GetSelectedThread();
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for process replaced events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -915,17 +907,17 @@ public class LldbManagerImpl implements LldbManager {
protected DebugStatus processProcessReplaced(LldbProcessReplacedEvent evt, Void v) {
DebugProcessInfo info = evt.getInfo();
SBProcess proc = info.process;
getEventListeners().fire.processReplaced(proc, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.processSelected(proc, evt.getCause());
getEventListeners().invoke().processReplaced(proc, LldbCause.Causes.UNCLAIMED);
getEventListeners().invoke().processSelected(proc, evt.getCause());
SBThread thread = proc.GetSelectedThread();
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
getEventListeners().invoke().threadSelected(thread, null, evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for process exited events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -933,73 +925,74 @@ public class LldbManagerImpl implements LldbManager {
protected DebugStatus processProcessExited(LldbProcessExitedEvent evt, Void v) {
SBThread thread = getCurrentThread();
SBProcess process = getCurrentProcess();
getEventListeners().fire.threadExited(thread, process, evt.getCause());
getEventListeners().fire.processExited(process, evt.getCause());
getEventListeners().fire.processRemoved(process.GetProcessID().toString(), evt.getCause());
getEventListeners().invoke().threadExited(thread, process, evt.getCause());
getEventListeners().invoke().processExited(process, evt.getCause());
getEventListeners().invoke()
.processRemoved(process.GetProcessID().toString(), evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for process selected events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
*/
protected DebugStatus processProcessSelected(LldbProcessSelectedEvent evt, Void v) {
currentProcess = evt.getProcess();
getEventListeners().fire.processSelected(currentProcess, evt.getCause());
getEventListeners().invoke().processSelected(currentProcess, evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for session created events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
*/
protected DebugStatus processSessionCreated(LldbSessionCreatedEvent evt, Void v) {
DebugSessionInfo info = evt.getInfo();
getEventListeners().fire.sessionAdded(info.session, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.sessionSelected(info.session, evt.getCause());
getEventListeners().invoke().sessionAdded(info.session, LldbCause.Causes.UNCLAIMED);
getEventListeners().invoke().sessionSelected(info.session, evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for session replaced events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
*/
protected DebugStatus processSessionReplaced(LldbSessionReplacedEvent evt, Void v) {
DebugSessionInfo info = evt.getInfo();
getEventListeners().fire.sessionReplaced(info.session, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.sessionSelected(info.session, evt.getCause());
getEventListeners().invoke().sessionReplaced(info.session, LldbCause.Causes.UNCLAIMED);
getEventListeners().invoke().sessionSelected(info.session, evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for session exited events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
*/
protected DebugStatus processSessionExited(LldbSessionExitedEvent evt, Void v) {
removeSession(evt.sessionId, LldbCause.Causes.UNCLAIMED);
getEventListeners().fire.sessionRemoved(evt.sessionId, evt.getCause());
getEventListeners().fire.threadExited(eventThread, eventProcess, evt.getCause());
getEventListeners().fire.processExited(eventProcess, evt.getCause());
getEventListeners().fire.processRemoved(eventProcess.GetProcessID().toString(),
evt.getCause());
getEventListeners().invoke().sessionRemoved(evt.sessionId, evt.getCause());
getEventListeners().invoke().threadExited(eventThread, eventProcess, evt.getCause());
getEventListeners().invoke().processExited(eventProcess, evt.getCause());
getEventListeners().invoke()
.processRemoved(eventProcess.GetProcessID().toString(), evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for module loaded events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -1009,14 +1002,14 @@ public class LldbManagerImpl implements LldbManager {
long n = info.getNumberOfModules();
SBProcess process = info.getProcess();
for (int i = 0; i < n; i++) {
getEventListeners().fire.moduleLoaded(process, info, i, evt.getCause());
getEventListeners().invoke().moduleLoaded(process, info, i, evt.getCause());
}
return statusMap.get(evt.getClass());
}
/**
* Handler for module unloaded events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -1026,14 +1019,14 @@ public class LldbManagerImpl implements LldbManager {
long n = info.getNumberOfModules();
SBProcess process = info.getProcess();
for (int i = 0; i < n; i++) {
getEventListeners().fire.moduleUnloaded(process, info, i, evt.getCause());
getEventListeners().invoke().moduleUnloaded(process, info, i, evt.getCause());
}
return statusMap.get(evt.getClass());
}
/**
* Handler for state changed events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -1092,20 +1085,20 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for session selected events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
*/
protected DebugStatus processSessionSelected(LldbSessionSelectedEvent evt, Void v) {
SBTarget session = evt.getSession();
getEventListeners().fire.sessionSelected(session, evt.getCause());
getEventListeners().invoke().sessionSelected(session, evt.getCause());
return statusMap.get(evt.getClass());
}
/**
* Handler for systems events
*
*
* @param evt the event
* @param v nothing
* @return retval handling/break status
@ -1116,13 +1109,13 @@ public class LldbManagerImpl implements LldbManager {
protected void processConsoleOutput(LldbConsoleOutputEvent evt, Void v) {
if (evt.getOutput() != null) {
getEventListeners().fire.consoleOutput(evt.getOutput(), evt.getMask());
getEventListeners().invoke().consoleOutput(evt.getOutput(), evt.getMask());
}
}
/**
* Handler for breakpoint-created event
*
*
* @param evt the event
* @param v nothing
*/
@ -1134,7 +1127,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-modified event
*
*
* @param evt the event
* @param v nothing
*/
@ -1146,7 +1139,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-deleted event
*
*
* @param evt the event
* @param v nothing
*/
@ -1157,7 +1150,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-enable event
*
*
* @param evt the event
* @param v nothing
*/
@ -1168,7 +1161,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-deleted event
*
*
* @param evt the event
* @param v nothing
*/
@ -1179,7 +1172,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-invalidated event
*
*
* @param evt the event
* @param v nothing
*/
@ -1191,7 +1184,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-modified event
*
*
* @param evt the event
* @param v nothing
*/
@ -1204,12 +1197,11 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-locations added event
*
*
* @param evt the event
* @param v nothing
*/
protected void processBreakpointLocationsAdded(LldbBreakpointLocationsAddedEvent evt,
Void v) {
protected void processBreakpointLocationsAdded(LldbBreakpointLocationsAddedEvent evt, Void v) {
SBTarget session = getCurrentSession();
Object info = evt.getBreakpointInfo();
doBreakpointModified(session, info, evt.getCause());
@ -1217,7 +1209,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-locations removed event
*
*
* @param evt the event
* @param v nothing
*/
@ -1230,7 +1222,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-modified event
*
*
* @param evt the event
* @param v nothing
*/
@ -1243,7 +1235,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-modified event
*
*
* @param evt the event
* @param v nothing
*/
@ -1255,7 +1247,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-modified event
*
*
* @param evt the event
* @param v nothing
*/
@ -1268,7 +1260,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-modified event
*
*
* @param evt the event
* @param v nothing
*/
@ -1280,7 +1272,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-modified event
*
*
* @param evt the event
* @param v nothing
*/
@ -1292,7 +1284,7 @@ public class LldbManagerImpl implements LldbManager {
/**
* Handler for breakpoint-modified event
*
*
* @param evt the event
* @param v nothing
*/
@ -1304,31 +1296,31 @@ public class LldbManagerImpl implements LldbManager {
/**
* Fire breakpoint created event
*
*
* @param newInfo the new information
* @param cause the cause of the creation
*/
@Internal
public void doBreakpointCreated(SBTarget session, Object info, LldbCause cause) {
addKnownBreakpoint(session, info, false);
getEventListeners().fire.breakpointCreated(info, cause);
getEventListeners().invoke().breakpointCreated(info, cause);
}
/**
* Fire breakpoint modified event
*
*
* @param newInfo the new information
* @param cause the cause of the modification
*/
@Internal
public void doBreakpointModified(SBTarget session, Object info, LldbCause cause) {
addKnownBreakpoint(session, info, true);
getEventListeners().fire.breakpointModified(info, cause);
getEventListeners().invoke().breakpointModified(info, cause);
}
/**
* Fire breakpoint deleted event
*
*
* @param number the deleted breakpoint number
* @param cause the cause of the deletion
*/
@ -1338,13 +1330,13 @@ public class LldbManagerImpl implements LldbManager {
if (oldInfo == null) {
return;
}
getEventListeners().fire.breakpointDeleted(oldInfo, cause);
getEventListeners().invoke().breakpointDeleted(oldInfo, cause);
}
protected void doBreakpointModifiedSameLocations(SBTarget session, Object info,
LldbCause cause) {
addKnownBreakpoint(session, info, true);
getEventListeners().fire.breakpointModified(info, cause);
getEventListeners().invoke().breakpointModified(info, cause);
}
@Internal
@ -1568,16 +1560,16 @@ public class LldbManagerImpl implements LldbManager {
Msg.warn(this, "defaulting to active thread");
return currentThread;
}
for (int i = 0; i < currentProcess.GetNumThreads(); i++) {
SBThread thread = currentProcess.GetThreadAtIndex(i);
if (thread.IsValid()) {
Msg.warn(this, "defaulting to thread "+i);
Msg.warn(this, "defaulting to thread " + i);
currentThread = thread;
break;
}
}
return currentThread;
}
@ -1638,7 +1630,7 @@ public class LldbManagerImpl implements LldbManager {
public CompletableFuture<Void> console(String command) {
if (continuation != null) {
String prompt = command.equals("") ? LldbModelTargetInterpreter.LLDB_PROMPT : ">>>";
getEventListeners().fire.promptChanged(prompt);
getEventListeners().invoke().promptChanged(prompt);
continuation.complete(command);
setContinuation(null);
return AsyncUtils.nil();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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() {

View file

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

View file

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

View file

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

View file

@ -27,15 +27,8 @@ import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.trace.model.Trace;
import ghidra.util.datastruct.ListenerSet;
@PluginInfo(
shortDescription = "Debugger targets manager service",
description = "Maintains a collection of published targets and notifies listeners of changes.",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
servicesProvided = {
DebuggerTargetService.class,
})
@PluginInfo(shortDescription = "Debugger targets manager service", description = "Maintains a collection of published targets and notifies listeners of changes.", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, servicesProvided = {
DebuggerTargetService.class, })
public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTargetService {
public DebuggerTargetServicePlugin(PluginTool tool) {
@ -44,7 +37,7 @@ public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTarge
private final Map<Trace, Target> targets = new HashMap<>();
private final ListenerSet<TargetPublicationListener> listeners =
new ListenerSet<>(TargetPublicationListener.class);
new ListenerSet<>(TargetPublicationListener.class, true);
@Override
public void publishTarget(Target target) {
@ -53,7 +46,7 @@ public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTarge
notify = targets.put(target.getTrace(), target) != target;
}
if (notify) {
listeners.fire.targetPublished(target);
listeners.invoke().targetPublished(target);
}
}
@ -64,7 +57,7 @@ public class DebuggerTargetServicePlugin extends Plugin implements DebuggerTarge
notify = targets.remove(target.getTrace()) == target;
}
if (notify) {
listeners.fire.targetWithdrawn(target);
listeners.invoke().targetWithdrawn(target);
}
}

View file

@ -34,8 +34,6 @@ import javax.swing.tree.TreePath;
import org.junit.*;
import org.junit.rules.TestName;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import db.Transaction;
import docking.ActionContext;
@ -81,7 +79,7 @@ import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.InvalidNameException;
import ghidra.util.datastruct.ListenerMap;
import ghidra.util.datastruct.TestDataStructureErrorHandlerInstaller;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.ConsoleTaskMonitor;
@ -103,8 +101,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
}
@Override
protected DebuggerRegisterMapper createRegisterMapper(
TargetRegisterContainer registers) {
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
return new DefaultDebuggerRegisterMapper(cSpec, registers, true);
}
}
@ -142,7 +139,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
/**
* Works like {@link #waitForValue(Supplier)}, except this caches {@link NoSuchElementException}
* and tries again.
*
*
* @param <T> the type of object to wait for
* @param supplier the supplier of the object
* @return the object
@ -172,7 +169,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
/**
* This is so gross
*
*
* @param lockable
*/
protected void waitForLock(DomainObject lockable) {
@ -184,7 +181,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
/**
* Get an address in the trace's default space
*
*
* @param trace the trace
* @param offset the byte offset in the default space
* @return the address
@ -195,7 +192,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
/**
* Get an address in the program's default space
*
*
* @param program the program
* @param offset the byte offset in the default space
* @return the address
@ -206,7 +203,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
/**
* Get an address range in the trace's default space
*
*
* @param program the program
* @param min the min byte offset in the default space
* @param max the max byte offset in the default space
@ -350,10 +347,10 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
/**
* Find the sub menu item of the current selection by text
*
*
* Note that if the desired item is at the same level as the currently selected item, this
* method will not find it. It searches the sub menu of the currently selected item.
*
*
* @param text the text
* @return the found item
* @throws NoSuchElementException if the desired item is not found
@ -365,7 +362,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
/**
* Activate via mouse the sub menu item of the current selection by text
*
*
* @param text the text on the item to click
* @throws AWTException
* @throws NoSuchElementException if no item with the given text is found
@ -481,9 +478,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
protected static void performEnabledAction(ActionContextProvider provider,
DockingActionIf action, boolean wait) {
ActionContext context = waitForValue(() -> {
ActionContext ctx = provider == null
? new DefaultActionContext()
: provider.getActionContext(null);
ActionContext ctx =
provider == null ? new DefaultActionContext() : provider.getActionContext(null);
if (!action.isEnabledForContext(ctx)) {
return null;
}
@ -556,15 +552,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
@Rule
public TestName name = new TestName();
@Rule
public TestWatcher watcher = new TestWatcher() {
@Override
protected void succeeded(Description description) {
if (description.isTest()) {
ListenerMap.checkErr();
}
}
};
protected final ConsoleTaskMonitor monitor = new ConsoleTaskMonitor();
protected void waitRecorder(TraceRecorder recorder) throws Throwable {
@ -586,9 +574,16 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
waitForDomainObject(recorder.getTrace());
}
@BeforeClass
public static void beforeClass() {
// Note: we may decided to move this up to a framework-level base test class
TestDataStructureErrorHandlerInstaller.installConcurrentExceptionErrorHandler();
}
@Before
public void setUp() throws Exception {
ListenerMap.clearErr();
env = new TestEnv();
tool = env.getTool();
@ -670,8 +665,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
populateTestModel();
TargetObject target = chooseTarget();
TraceRecorder recorder = modelService.recordTarget(target,
createTargetTraceMapper(target), ActionSource.AUTOMATIC);
TraceRecorder recorder = modelService.recordTarget(target, createTargetTraceMapper(target),
ActionSource.AUTOMATIC);
waitRecorder(recorder);
return recorder;
@ -682,9 +677,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
protected void intoProject(DomainObject obj) {
waitForDomainObject(obj);
DomainFolder rootFolder = tool.getProject()
.getProjectData()
.getRootFolder();
DomainFolder rootFolder = tool.getProject().getProjectData().getRootFolder();
waitForCondition(() -> {
try {
rootFolder.createFile(obj.getName(), obj, monitor);
@ -799,8 +792,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged);
}
void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range,
byte[] oldValue, byte[] newValue) {
void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range, byte[] oldValue,
byte[] newValue) {
if (space.getThread() != traceThread) {
return;
}

View file

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

View file

@ -32,12 +32,12 @@ import ghidra.util.datastruct.ListenerSet;
public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectModel {
public final Object lock = new Object();
public final Object cbLock = new Object();
protected final ExecutorService clientExecutor =
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
.namingPattern(getClass().getSimpleName() + "-thread-%d")
protected final ExecutorService clientExecutor = Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder().namingPattern(getClass().getSimpleName() + "-thread-%d")
.build());
protected final ListenerSet<DebuggerModelListener> listeners =
new ListenerSet<>(DebuggerModelListener.class, clientExecutor);
new ListenerSet<>(DebuggerModelListener.class, true);
protected SpiTargetObject root;
protected boolean rootAdded;
@ -98,7 +98,10 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
return null;
});
this.completedRoot.completeAsync(() -> root, clientExecutor);
listeners.fire.rootAdded(root);
clientExecutor.execute(() -> {
listeners.invoke().rootAdded(root);
});
}
}
@ -200,7 +203,7 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
/**
* Ensure that dependent computations occur on the client executor
*
*
* @param <T> the type of the future value
* @param v the future
* @return a future which completes after the given one on the client executor
@ -249,13 +252,11 @@ public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectMo
}
DefaultTargetObject<?, ?> dtoParent = (DefaultTargetObject<?, ?>) delegate;
if (PathUtils.isIndex(path)) {
dtoParent.changeElements(List.of(PathUtils.getIndex(path)), List.of(),
"Replaced");
dtoParent.changeElements(List.of(PathUtils.getIndex(path)), List.of(), "Replaced");
}
else {
assert PathUtils.isName(path);
dtoParent.changeAttributes(List.of(PathUtils.getKey(path)), Map.of(),
"Replaced");
dtoParent.changeAttributes(List.of(PathUtils.getKey(path)), Map.of(), "Replaced");
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -84,7 +84,7 @@ public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
double pos =
span * (e.getX() - colX) / myViewCol.getWidth() + fullRangeDouble.min();
listeners.fire.accept(pos);
listeners.invoke().accept(pos);
}
}
@ -105,7 +105,7 @@ public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
private int savedViewColumn;
private final ForSeekMouseListener forSeekMouseListener = new ForSeekMouseListener();
private final ListenerSet<SeekListener> listeners = new ListenerSet<>(SeekListener.class);
private final ListenerSet<SeekListener> listeners = new ListenerSet<>(SeekListener.class, true);
public RangeCursorTableHeaderRenderer(N pos) {
this.pos = pos;

View file

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

View file

@ -1,304 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import java.lang.ref.WeakReference;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import ghidra.util.Msg;
/**
* A map of listeners and a proxy for invoking each
*
* <P>
* This is effectively a multiplexing primitive for a collection of listeners. The listeners may be
* indexed by some key other than the listeners themselves. This is often useful if a filter or
* wrapper is applied. If no wrapper is applied, consider using {@link ListenerSet} instead.
* Additionally, the map is weak keyed, so that listeners are automatically removed if nothing else
* maintain a strong reference.
*
* <P>
* The proxy is accessed via the public {@link #fire} field. This implements the same interfaces as
* each listener in the collection. Any method invoked on this proxy is invoked upon each listener
* in the collection. If any invocation results in an unexpected exception, that exception is
* logged, but otherwise ignored. This protects callbacks from errors introduced by other callbacks.
* Expected exceptions are those declared in the {@code throws} clause of the invoked method. Such
* an exception is immediately rethrown, preventing the execution of further callbacks. The default
* implementation of {@link #createMap()} returns a synchronized map. The return value of any
* invoked listener is ignored. Every invocation on the proxy returns null. As such, it is advisable
* to only invoke proxy methods which return {@code void}.
*
* @param <K> the type of keys
* @param <P> the interface of the proxy and multiplexed listeners
* @param <V> the type of listeners
*/
public class ListenerMap<K, P, V extends P> {
private static final boolean DEBUG_INCEPTION = false;
public static class ListenerEntry<V> extends WeakReference<V> {
final String desc;
final Throwable inception;
public ListenerEntry(V referent) {
super(referent);
this.desc = referent.toString();
if (DEBUG_INCEPTION) {
this.inception = new Throwable();
}
else {
this.inception = null;
}
}
}
public static final Executor CALLING_THREAD = new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
};
protected static final AtomicReference<Throwable> firstExc = new AtomicReference<>();
protected static void reportError(Object listener, Throwable e) {
if (e instanceof RejectedExecutionException) {
Msg.trace(listener, "Listener invocation rejected: " + e);
}
else {
Msg.error(listener, "Listener " + listener + " caused unexpected exception", e);
firstExc.accumulateAndGet(e, (o, n) -> o == null ? n : o);
}
}
/**
* Clear the recorded exception.
*
* <P>
* This method is for testing. If listeners are involved in a test, then this should be called
* before that test.
*
* @see #checkErr()
*/
public static void clearErr() {
firstExc.set(null);
}
/**
* Check and clear the recorded exception.
*
* <P>
* This method is for testing. If listeners are involved in a test, then this should be called
* after that test.
*
* <P>
* Listeners are often invoked in threads off the test thread. Thus, if they generate an
* exception, they get logged, but are otherwise ignored. In particular, a JUnit test with a
* listener-generated exception will likely still pass (assuming no other assertion fails). This
* method allows such exceptions to be detected and properly cause test failure. Note that this
* only works for listeners derived from {@link ListenerMap}, including {@link ListenerSet}.
* When an exception is logged, it is also recorded (statically) in the {@link ListenerMap}
* class. Only the <em>first</em> unhandled exception is recorded. Subsequent exceptions are
* logged, but ignored, until that first exception is cleared and/or checked.
*/
public static void checkErr() {
Throwable exc = firstExc.getAndSet(null);
if (exc != null) {
throw new AssertionError("Listener caused an exception", exc);
}
}
protected class ListenerHandler<T extends P> implements InvocationHandler {
protected final Class<T> ext;
public ListenerHandler(Class<T> ext) {
this.ext = ext;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//Msg.debug(this, "Queuing invocation: " + method.getName() + " @" +
// System.identityHashCode(executor));
// Listener adds/removes need to take immediate effect, even with queued events
executor.execute(() -> {
Collection<? extends ListenerEntry<? extends V>> listenersVolatile;
synchronized (lock) {
listenersVolatile = map.values();
}
for (ListenerEntry<? extends V> wl : listenersVolatile) {
V l = wl.get();
if (l == null || !ext.isAssignableFrom(l.getClass())) {
continue;
}
//Msg.debug(this,
// "Invoking: " + method.getName() + " @" + System.identityHashCode(executor));
try {
method.invoke(l, args);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
reportError(l, cause);
}
catch (Throwable e) {
reportError(l, e);
}
}
});
return null; // TODO: Assumes void return type
}
}
private final Object lock = new Object();
private final Class<P> iface;
private final Executor executor;
private Map<K, ? extends ListenerEntry<? extends V>> map = createMap();
/**
* A proxy which passes invocations to each value of this map
*/
public final P fire;
/**
* A map of cached specialized proxies
*/
protected final Map<Class<? extends P>, P> extFires = new HashMap<>();
/**
* Construct a new map whose proxy implements the given interface
*
* <P>
* The values in the map must implement the same interface.
*
* <P>
* Callbacks will be serviced by the invoking thread. This may be risking if the invoking thread
* is "precious" to the invoker. There is no guarantee callbacks into client code will complete
* in a timely fashion.
*
* @param iface the interface to multiplex
*/
public ListenerMap(Class<P> iface) {
this(iface, CALLING_THREAD);
}
/**
* Construct a new map whose proxy implements the given interface
*
* <P>
* The values in the map must implement the same interface.
*
* @param iface the interface to multiplex
*/
public ListenerMap(Class<P> iface, Executor executor) {
this.iface = Objects.requireNonNull(iface);
this.executor = executor;
this.fire = iface.cast(Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[] { iface }, new ListenerHandler<>(iface)));
}
@Override
public String toString() {
return map.toString();
}
protected Map<K, ListenerEntry<? extends V>> createMap() {
return new HashMap<>();
}
protected void notifyRemoved(ListenerEntry<? extends V> entry) {
Msg.warn(this, "Listener garbage collected before removal: " + entry.desc);
}
@SuppressWarnings("unchecked")
public <T extends P> T fire(Class<T> ext) {
if (ext == iface) {
return ext.cast(fire);
}
if (!iface.isAssignableFrom(ext)) {
throw new IllegalArgumentException("Cannot fire on less-specific interface");
}
return (T) extFires.computeIfAbsent(ext,
e -> (P) Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class<?>[] { iface, ext }, new ListenerHandler<>(ext)));
}
public boolean isEmpty() {
return map.isEmpty();
}
protected void doPutAllInto(Map<? super K, ? super ListenerEntry<? extends V>> newMap) {
for (Entry<K, ? extends ListenerEntry<? extends V>> ent : map.entrySet()) {
if (ent.getValue().get() == null) {
notifyRemoved(ent.getValue());
}
else {
newMap.put(ent.getKey(), ent.getValue());
}
}
}
public V put(K key, V val) {
synchronized (lock) {
if (map.get(key) == val) {
return val;
}
Map<K, ListenerEntry<? extends V>> newMap = createMap();
doPutAllInto(newMap);
ListenerEntry<? extends V> result = newMap.put(key, new ListenerEntry<>(val));
map = newMap;
return result == null ? null : result.get();
}
}
public void putAll(ListenerMap<? extends K, P, ? extends V> that) {
synchronized (lock) {
Map<K, ListenerEntry<? extends V>> newMap = createMap();
doPutAllInto(newMap);
that.doPutAllInto(newMap);
map = newMap;
}
}
public V get(K key) {
ListenerEntry<? extends V> entry = map.get(key);
return entry == null ? null : entry.get();
}
public V remove(K key) {
synchronized (lock) {
if (!map.containsKey(key)) {
return null;
}
Map<K, ListenerEntry<? extends V>> newMap = createMap();
doPutAllInto(newMap);
ListenerEntry<? extends V> result = newMap.remove(key);
map = newMap;
return result == null ? null : result.get();
}
}
public void clear() {
synchronized (lock) {
if (map.isEmpty()) {
return;
}
map = createMap();
}
}
}

View file

@ -1,102 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import ghidra.util.datastruct.ListenerMap.ListenerEntry;
/**
* A weak set of multiplexed listeners and an invocation proxy
*
* @param <E> the type of multiplexed listeners
*/
public class ListenerSet<E> {
public static final Executor CALLING_THREAD = ListenerMap.CALLING_THREAD;
private final ListenerMap<E, E, E> map;
/**
* A proxy which passes invocations to each member of this set
*/
public final E fire;
/**
* Construct a new set whose elements and proxy implement the given interface
*
* <p>
* Callbacks will be serviced by the invoking thread. This may be risking if the invoking thread
* is "precious" to the invoker. There is no guarantee callbacks into client code will complete
* in a timely fashion.
*
* @param iface the interface to multiplex
*/
public ListenerSet(Class<E> iface) {
this(iface, CALLING_THREAD);
}
/**
* Construct a new set whose elements and proxy implement the given interface
*
* @param iface the interface to multiplex
* @param executor an executor for servicing callbacks
*/
public ListenerSet(Class<E> iface, Executor executor) {
map = new ListenerMap<E, E, E>(iface, executor) {
@Override
protected Map<E, ListenerEntry<? extends E>> createMap() {
return ListenerSet.this.createMap();
};
};
fire = map.fire;
}
@Override
public String toString() {
return map.toString();
}
protected Map<E, ListenerEntry<? extends E>> createMap() {
// TODO: This doesn't prevent the automatic removal of an entry in the "immutable" map.
return new WeakHashMap<>();
}
public <T extends E> T fire(Class<T> ext) {
return map.fire(ext);
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean add(E e) {
return map.put(e, e) != e;
}
@SuppressWarnings("unchecked")
public void addAll(ListenerSet<? extends E> c) {
map.putAll((ListenerMap<? extends E, E, ? extends E>) c.map);
}
public boolean remove(E e) {
return map.remove(e) == e;
}
public void clear() {
map.clear();
}
}

View file

@ -16,6 +16,7 @@
package ghidra.util.datastruct;
import java.lang.reflect.*;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@ -28,6 +29,9 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
*/
public class PrivatelyQueuedListener<P> {
private ListenerErrorHandler errorHandler =
DataStructureErrorHandlerFactory.createListenerErrorHandler();
protected class ListenerHandler implements InvocationHandler {
private static final Method OBJECT_HASHCODE;
static {
@ -55,10 +59,10 @@ public class PrivatelyQueuedListener<P> {
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
ListenerMap.reportError(out, cause);
errorHandler.handleError(out, cause);
}
catch (Throwable e) {
ListenerMap.reportError(out, e);
errorHandler.handleError(out, e);
}
});
return null; // Assumes void return type
@ -75,7 +79,7 @@ public class PrivatelyQueuedListener<P> {
/**
* Create a new privately-queued listener which will invoke the given "output" listener
*
*
* <p>
* Invoking the listener methods of {@link #in} will cause that invocation to be queued and
* eventually delivered to the given output listener. Note, as a result, it is assumed all
@ -83,7 +87,7 @@ public class PrivatelyQueuedListener<P> {
* the invocation to complete, which defeats the purpose of the private queue. The invocations
* on {@link #in} will always return {@code null}, which will cause an exception if the return
* type is a different primitive.
*
*
* @param iface the interface of the listener
* @param executor the executor representing the processing queue
* @param out the listener to receive the queued invocations
@ -97,17 +101,17 @@ public class PrivatelyQueuedListener<P> {
/**
* Create a new single-threaded privately-queued listener
*
* @see {@link #PrivatelyQueuedListener(Class, Executor, Object)}
*
* @param iface the interface of the listener
* @param threadNamePattern a pattern for naming the single thread
* @param out the listener to receive the queued invocations
*/
public PrivatelyQueuedListener(Class<P> iface, String threadNamePattern, P out) {
this(iface,
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
.namingPattern(threadNamePattern)
.build()),
out);
this(iface, Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder().namingPattern(threadNamePattern).build()), out);
}
public void setErrorHandler(ListenerErrorHandler errorHandler) {
this.errorHandler = Objects.requireNonNull(errorHandler);
}
}

View file

@ -1,173 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
public class ListenerMapTest {
public interface DummyListener {
void event(String e);
}
@Test
public void testBehavesLikeMap() {
ListenerMap<String, DummyListener, DummyListener> listeners =
new ListenerMap<>(DummyListener.class);
DummyListener d1 = e -> {
};
DummyListener d2 = e -> {
};
listeners.put("Key1", d1);
listeners.put("Key2", d2);
assertEquals(d1, listeners.get("Key1"));
assertEquals(d2, listeners.get("Key2"));
listeners.put("Key1", d2);
assertEquals(d2, listeners.get("Key1"));
}
@Test
public void testMultiplexes() {
ListenerMap<String, DummyListener, DummyListener> listeners =
new ListenerMap<>(DummyListener.class);
AtomicReference<String> ar1 = new AtomicReference<>();
listeners.put("Key1", ar1::set);
listeners.fire.event("EventA");
assertEquals("EventA", ar1.get());
AtomicReference<String> ar2 = new AtomicReference<>();
listeners.put("Key2", ar2::set);
listeners.fire.event("EventB");
assertEquals("EventB", ar1.get());
assertEquals("EventB", ar2.get());
AtomicReference<String> ar3 = new AtomicReference<>();
listeners.put("Key1", ar3::set); // Overwrite Key1
listeners.fire.event("EventC");
assertEquals("EventB", ar1.get());
assertEquals("EventC", ar2.get());
assertEquals("EventC", ar3.get());
}
protected void waitEvents(Executor executor) throws Throwable {
CompletableFuture.runAsync(() -> {
}, executor).get(1000, TimeUnit.MILLISECONDS);
}
@Test
public void testAddsRemovesImmediatelyEffective() throws Throwable {
Executor executor = Executors.newSingleThreadExecutor();
CompletableFuture.runAsync(() -> Thread.currentThread().setName("ExecutorThread"), executor)
.get();
ListenerMap<String, DummyListener, DummyListener> listeners =
new ListenerMap<>(DummyListener.class, executor);
Map<String, CompletableFuture<?>> stalls = Map.ofEntries(
Map.entry("StallA", new CompletableFuture<>()),
Map.entry("StallB", new CompletableFuture<>()),
Map.entry("StallD", new CompletableFuture<>()));
AtomicReference<String> ar1 = new AtomicReference<>();
DummyListener l1 = s -> {
CompletableFuture<?> stall = stalls.get(s);
if (stall != null) {
try {
stall.get();
}
catch (InterruptedException | ExecutionException e) {
// Nothing I really can do
}
}
ar1.set(s);
};
AtomicReference<String> ar2 = new AtomicReference<>();
DummyListener l2 = ar2::set;
listeners.put("Key1", l1);
ar1.set("None");
listeners.fire.event("StallA");
assertEquals("None", ar1.get());
stalls.get("StallA").complete(null);
waitEvents(executor);
assertEquals("StallA", ar1.get());
// NB. It's the the fire timeline that matters, but the completion timeline
listeners.fire.event("StallB");
listeners.fire.event("EventC");
listeners.put("Key2", l2);
stalls.get("StallB").complete(null);
waitEvents(executor);
assertEquals("EventC", ar1.get());
assertEquals("EventC", ar2.get());
listeners.fire.event("StallD");
listeners.fire.event("EventE");
listeners.remove("Key2");
stalls.get("StallD").complete(null);
waitEvents(executor);
assertEquals("EventE", ar1.get());
assertNotEquals("EventE", ar2.get());
}
@Test
public void testContinuesOnError() {
ListenerMap<String, DummyListener, DummyListener> listeners =
new ListenerMap<>(DummyListener.class);
AtomicReference<String> ar1 = new AtomicReference<>();
DummyListener d1 = e -> {
ar1.set(e);
throw new RuntimeException("It had better continue (1)");
};
listeners.put("Key1", d1);
AtomicReference<String> ar2 = new AtomicReference<>();
DummyListener d2 = e -> {
ar2.set(e);
throw new RuntimeException("It had better continue (2)");
};
listeners.put("Key2", d2);
listeners.fire.event("Should see on both");
assertEquals("Should see on both", ar1.get());
assertEquals("Should see on both", ar2.get());
}
@Test
public void testWeaklyReferencesListeners() {
ListenerMap<String, DummyListener, DummyListener> listeners =
new ListenerMap<>(DummyListener.class);
AtomicReference<String> ar1 = new AtomicReference<>();
DummyListener d1 = e -> {
ar1.set(e);
throw new RuntimeException("It had better continue (1)");
};
listeners.put("Key1", d1);
listeners.fire.event("EventA");
assertEquals("EventA", ar1.get());
d1 = null; // Trash the only strong reference
System.gc();
listeners.fire.event("EventB");
assertEquals("EventA", ar1.get());
}
}

View file

@ -1,98 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
public class ListenerSetTest {
public interface DummyListener {
void event(String e);
}
@Test
public void testBehavesLikeSetAndMultiplexes() {
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class);
AtomicInteger ai1 = new AtomicInteger();
DummyListener d1 = e -> {
ai1.getAndIncrement();
};
AtomicInteger ai2 = new AtomicInteger();
DummyListener d2 = e -> {
ai2.getAndIncrement();
};
listeners.add(d1);
listeners.add(d2);
listeners.fire.event("EventA");
assertEquals(1, ai1.get());
assertEquals(1, ai2.get());
listeners.add(d1); // This had better not double fire
listeners.fire.event("EventB");
assertEquals(2, ai1.get());
assertEquals(2, ai2.get());
}
@Test
public void testContinuesOnError() {
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class);
AtomicReference<String> ar1 = new AtomicReference<>();
DummyListener d1 = e -> {
ar1.set(e);
throw new RuntimeException("It had better continue (1)");
};
listeners.add(d1);
AtomicReference<String> ar2 = new AtomicReference<>();
DummyListener d2 = e -> {
ar2.set(e);
throw new RuntimeException("It had better continue (2)");
};
listeners.add(d2);
listeners.fire.event("Should see on both");
assertEquals("Should see on both", ar1.get());
assertEquals("Should see on both", ar2.get());
}
@Test
public void testWeaklyReferencesListeners() {
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class);
AtomicReference<String> ar1 = new AtomicReference<>();
DummyListener d1 = e -> {
ar1.set(e);
throw new RuntimeException("It had better continue (1)");
};
listeners.add(d1);
listeners.fire.event("EventA");
assertEquals("EventA", ar1.get());
d1 = null; // Trash the only strong reference
System.gc();
listeners.fire.event("EventB");
assertEquals("EventA", ar1.get());
}
}

View file

@ -22,7 +22,7 @@ import java.util.*;
import org.apache.commons.lang3.StringUtils;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.Swing;
/**
* A class which handles exceptions that occur off of the main test thread. Exceptions can be
@ -33,7 +33,7 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
// Exception messages that we choose to ignore
private static final String[] IGNORABLE_ERROR_MESSAGES =
new String[] { "DerivedColor$UIResource cannot be cast to", // test machine timing issue
"FontUIResource cannot be cast to javax.swing.Painter", // test machine timing issue
"FontUIResource cannot be cast to javax.swing.Painter", // test machine timing issue
};
private static final List<TestExceptionTracker> throwables =
@ -41,13 +41,23 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
private static volatile boolean enabled = true;
/**
* Installs this exception handler as the default uncaught exception handler. See
* {@link Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)}
*/
public static void registerHandler() {
SystemUtilities.runSwingLater(() -> {
// do this on the Swing thread
// Note: not sure why this is done on the Swing thread later. Seems like this could be done
// when this method is called, from any thread.
Swing.runLater(() -> {
Thread.setDefaultUncaughtExceptionHandler(new ConcurrentTestExceptionHandler());
});
}
/**
* Tells this class to process the given throwable
* @param thread the thread that encountered the throwable
* @param t the throwable
*/
public synchronized static void handle(Thread thread, Throwable t) {
if (!enabled) {
@ -72,10 +82,10 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
/**
* Some exceptions that happen off the test thread are not serious enough to fail the test.
* For example, some exceptions happen on the headless test server due more to
* environmental issues rather than real problems. This method is intended to ignore
* For example, some exceptions happen on the headless test server due more to
* environmental issues rather than real problems. This method is intended to ignore
* these less-than-serious issues.
*
*
* @param t the throwable to examine
* @return true if it should be ignored
*/
@ -89,26 +99,49 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
return StringUtils.containsAny(message, IGNORABLE_ERROR_MESSAGES);
}
/**
* Clears all exceptions being tracked by this class
*/
public synchronized static void clear() {
throwables.clear();
}
/**
* Enables this class after a call to {@link #disable()} has been made
*/
public synchronized static void enable() {
enabled = true;
}
/**
* Disables this class's tracking of exceptions. Clients use this method to have this class
* ignore expected exceptions. This is a bit course-grained, as it does not allow clients to
* ignore specific expected exceptions.
*/
public synchronized static void disable() {
enabled = false;
}
/**
* Returns true if this class is enabled. When disabled this class does not track exceptions.
* @return true if enabled
*/
public synchronized static boolean isEnabled() {
return enabled;
}
/**
* Returns all exceptions tracked by this class
* @return all exceptions tracked by this class
*/
public static synchronized List<TestExceptionTracker> getExceptions() {
return new ArrayList<>(throwables);
}
/**
* Returns true if this class has been given any exceptions to handle since last being cleared
* @return true if this class has been given any exceptions to handle since last being cleared
*/
public static synchronized boolean hasException() {
return !throwables.isEmpty();
}

View file

@ -38,14 +38,18 @@ class CopyOnReadWeakSet<T> extends WeakSet<T> {
}
@Override
public synchronized void add(T t) {
public synchronized boolean add(T t) {
maybeWarnAboutAnonymousValue(t);
boolean contains = weakHashStorage.containsKey(t);
weakHashStorage.put(t, null);
return !contains;
}
@Override
public synchronized void remove(T t) {
public synchronized boolean remove(Object t) {
boolean contains = weakHashStorage.containsKey(t);
weakHashStorage.remove(t);
return contains;
}
@Override
@ -64,7 +68,7 @@ class CopyOnReadWeakSet<T> extends WeakSet<T> {
}
@Override
public synchronized boolean contains(T t) {
public synchronized boolean contains(Object t) {
return weakHashStorage.containsKey(t);
}
@ -88,4 +92,32 @@ class CopyOnReadWeakSet<T> extends WeakSet<T> {
return createCopy().stream();
}
@Override
public synchronized boolean addAll(Collection<? extends T> c) {
boolean changed = false;
for (T t : c) {
changed |= add(t);
}
return changed;
}
@Override
public synchronized boolean retainAll(Collection<?> c) {
boolean changed = false;
Iterator<T> it = iterator();
while (it.hasNext()) {
T t = it.next();
if (!c.contains(t)) {
it.remove();
changed = true;
}
}
return changed;
}
@Override
public synchronized boolean removeAll(Collection<?> c) {
return weakHashStorage.keySet().removeAll(c);
}
}

View file

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

View file

@ -0,0 +1,48 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import ghidra.util.Msg;
/**
* A class data structures can use to delegate error handling responsibilities to system-level
* decision making. This allows for specialized error handling in testing mode.
*/
public class DataStructureErrorHandlerFactory {
// This field can be changed by the testing framework
static ListenerErrorHandlerFactory listenerFactory = new ListenerErrorHandlerFactory() {
@Override
public ListenerErrorHandler createErrorHandler() {
return new DefaultListenerErrorHandler();
}
};
/**
* Creates a {@link ListenerErrorHandler}
* @return the error handler
*/
public static ListenerErrorHandler createListenerErrorHandler() {
return listenerFactory.createErrorHandler();
}
private static class DefaultListenerErrorHandler implements ListenerErrorHandler {
@Override
public void handleError(Object listener, Throwable t) {
Msg.error(listener, "Listener " + listener + " caused unexpected exception", t);
}
}
}

View file

@ -0,0 +1,29 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
/**
* A simple interface that allows listener structures to use different error handling
*/
public interface ListenerErrorHandler {
/**
* Handles the given error
* @param listener the listener that generated the error
* @param t the error
*/
public void handleError(Object listener, Throwable t);
}

View file

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
/**
* A simple interface for creating listener error handlers
*/
public interface ListenerErrorHandlerFactory {
/**
* Creates the error handler
* @return the error handler
*/
public ListenerErrorHandler createErrorHandler();
}

View file

@ -0,0 +1,148 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import java.lang.reflect.*;
import java.util.Objects;
/**
* A data structure meant to be used to hold listeners. This class has a few benefits:
* <ul>
* <li>Clients supply the class of the listeners being stored. Then, clients make use of a Java
* {@link Proxy} object to sends events by calling the desired method directly on the proxy.
* </li>
* <li>This class is thread safe, allowing adding and removing listeners while events are being
* fired.
* </li>
* <li>Weak or strong references may be used seamlessly by passing the correct constructor value.
* </li>
* </ul>
*
* <p>
* Some restrictions:
* <ul>
* <li>Exception handling is currently done by storing the first exception encountered while
* processing events. Any exception encountered while notifying a listener does not stop
* follow-on listeners from getting notified.
* </li>
* <li>Listener classes are restricted to using methods with a void return type, as there is
* currently no way to return values back to the client when notifying.
* </li>
* <li>The insertion order of listeners is not maintained, which means that event notification may
* take place in an arbitrary order.
* </li>
* </ul>
*
* <p>
* An example use of this class to fire events could look like this:
* <pre>
* ListenerSet&lt;ActionListener&gt; listeners = new ListenerSet(ActionListener.class);
* ActionEvent event = new ActionEvent(this, 1, "Event");
* listeners.invoke().actionPerformed(event);
* </pre>
*
* @param <T> the listener type
*/
public class ListenerSet<T> {
/**
* A proxy which passes invocations to each member of this set
*/
private final T proxy;
private final ThreadSafeListenerStorage<T> listeners;
private ListenerErrorHandler errorHandler =
DataStructureErrorHandlerFactory.createListenerErrorHandler();
/**
* Constructs a listener set that is backed by weak references.
* @param iface the listener class type.
* @param isWeak true signals to use weak storage for the listeners. If using weak storage,
* clients must keep a reference to the listener or it will eventually be removed from
* this data structure when garbage collected.
*/
public ListenerSet(Class<T> iface, boolean isWeak) {
Objects.requireNonNull(iface);
this.proxy = iface.cast(Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[] { iface }, new ListenerHandler()));
this.listeners = new ThreadSafeListenerStorage<>(isWeak);
}
private class ListenerHandler implements InvocationHandler {
@Override
public Object invoke(Object proxyObject, Method method, Object[] args) throws Throwable {
listeners.forEach(listener -> {
try {
method.invoke(listener, args);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
errorHandler.handleError(listener, cause);
}
catch (Throwable e) {
errorHandler.handleError(listener, e);
}
});
return null; // assumes void return type
}
}
/**
* Returns the proxy object. Using this is the same as calling {@link #getProxy()}. Use this
* method to make the client call more readable.
*
* @return the proxy
*/
public T invoke() {
return proxy;
}
/**
* Returns the proxy used by this class. Using {@link #invoke()} is preferred for better
* readability.
* @return the proxy
*/
public T getProxy() {
return proxy;
}
@Override
public String toString() {
return listeners.toString();
}
public boolean add(T e) {
return listeners.add(e);
}
public boolean remove(T e) {
return listeners.remove(e);
}
public void clear() {
listeners.clear();
}
public int size() {
return listeners.size();
}
public void setErrorHandler(ListenerErrorHandler errorHandler) {
this.errorHandler = Objects.requireNonNull(errorHandler);
}
}

View file

@ -0,0 +1,126 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import generic.cache.Factory;
/**
* A very specific data structure that provides 'copy on write' behavior while the client is
* iterating the elements.
* <p>
* This class is meant for a very narrow and specific use case that includes: having a relatively
* small number of listeners and the need for only basic adding, removing and iterating.
* <p>
* This class will create a new copy of its internal storage for any write operation, but only if
* that happens while the elements in this class are being iterated. This avoids unnecessary
* copying.
*
* @param <T> the storage type
*/
class ThreadSafeListenerStorage<T> {
// Creates a new set and adds all values from the optional set argument
private Factory<Set<T>, Set<T>> factory;
private Set<T> storage;
private AtomicInteger iteratorCount = new AtomicInteger();
ThreadSafeListenerStorage(boolean isWeak) {
this(createFactory(isWeak));
}
ThreadSafeListenerStorage(Factory<Set<T>, Set<T>> factory) {
this.factory = factory;
this.storage = factory.get(null);
}
void forEach(Consumer<T> c) {
Set<T> toIterate = getSet();
try {
for (T t : toIterate) {
c.accept(t);
}
}
finally {
iteratorCount.decrementAndGet();
}
}
private synchronized Set<T> getSet() {
iteratorCount.incrementAndGet();
return storage;
}
synchronized boolean add(T t) {
if (iteratorCount.get() != 0) {
storage = factory.get(storage);
}
return storage.add(t);
}
synchronized boolean remove(Object t) {
if (iteratorCount.get() != 0) {
storage = factory.get(storage);
}
return storage.remove(t);
}
synchronized void clear() {
storage = factory.get(null);
}
synchronized int size() {
return storage.size();
}
private static <T> Factory<Set<T>, Set<T>> createFactory(boolean isWeak) {
if (isWeak) {
return new WeakSetFactory<T>();
}
return new StrongSetFactory<T>();
}
private static class WeakSetFactory<T> implements Factory<Set<T>, Set<T>> {
@Override
public Set<T> get(Set<T> set) {
Set<T> newSet = new ThreadUnsafeWeakSet<>();
if (set != null) {
newSet.addAll(set);
}
return newSet;
}
}
private static class StrongSetFactory<T> implements Factory<Set<T>, Set<T>> {
@Override
public Set<T> get(Set<T> set) {
Set<T> newSet = new HashSet<>();
if (set != null) {
newSet.addAll(set);
}
return newSet;
}
}
}

View file

@ -26,14 +26,18 @@ class ThreadUnsafeWeakSet<T> extends WeakSet<T> {
}
@Override
public void add(T t) {
public boolean add(T t) {
maybeWarnAboutAnonymousValue(t);
boolean contains = weakHashStorage.containsKey(t);
weakHashStorage.put(t, null);
return !contains;
}
@Override
public void remove(T t) {
public boolean remove(Object t) {
boolean contains = weakHashStorage.containsKey(t);
weakHashStorage.remove(t);
return contains;
}
@Override
@ -62,7 +66,7 @@ class ThreadUnsafeWeakSet<T> extends WeakSet<T> {
}
@Override
public boolean contains(T t) {
public boolean contains(Object t) {
return weakHashStorage.containsKey(t);
}
@ -76,4 +80,31 @@ class ThreadUnsafeWeakSet<T> extends WeakSet<T> {
return values().stream();
}
@Override
public boolean addAll(Collection<? extends T> c) {
boolean changed = false;
for (T t : c) {
changed |= add(t);
}
return changed;
}
@Override
public boolean retainAll(Collection<?> c) {
boolean changed = false;
Iterator<T> it = iterator();
while (it.hasNext()) {
T t = it.next();
if (!c.contains(t)) {
it.remove();
changed = true;
}
}
return changed;
}
@Override
public boolean removeAll(Collection<?> c) {
return weakHashStorage.keySet().removeAll(c);
}
}

View file

@ -36,16 +36,6 @@ public class WeakDataStructureFactory {
return new ThreadUnsafeWeakSet<>();
}
/**
* Use to signal that the returned weak set is not thread safe and must be protected accordingly
* when used in a multi-threaded environment.
*
* @return a new WeakSet
*/
public static <T> WeakSet<T> createThreadUnsafeWeakSet() {
return new ThreadUnsafeWeakSet<>();
}
/**
* Use when mutations outweigh iterations.
*

View file

@ -17,14 +17,13 @@ package ghidra.util.datastruct;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.WeakHashMap;
import java.util.*;
import java.util.stream.Stream;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
public abstract class WeakSet<T> implements Iterable<T> {
public abstract class WeakSet<T> implements Set<T> {
private static final boolean WARN_ON_ANONYMOUS_VALUE =
SystemUtilities.isInDevelopmentMode() || SystemUtilities.isInTestingMode();
@ -78,51 +77,47 @@ public abstract class WeakSet<T> implements Iterable<T> {
// Interface Methods
//==================================================================================================
/**
* Adds all items to this set
* @param it the items
*/
public void addAll(Iterable<T> it) {
for (T t : it) {
add(t);
}
}
/**
* Add the given object to the set
* @param t the object to add
*/
public abstract void add(T t);
@Override
public abstract boolean add(T t);
/**
* Remove the given object from the data structure
* @param t the object to remove
*
*/
public abstract void remove(T t);
@Override
public abstract boolean remove(Object t);
/**
* Returns true if the given object is in this data structure
* @param t the object
* @return true if the given object is in this data structure
*/
public abstract boolean contains(T t);
@Override
public abstract boolean contains(Object t);
/**
* Remove all elements from this data structure
*/
@Override
public abstract void clear();
/**
* Return the number of objects contained within this data structure
* @return the size
*/
@Override
public abstract int size();
/**
* Return whether this data structure is empty
* @return whether this data structure is empty
*/
@Override
public abstract boolean isEmpty();
/**
@ -132,9 +127,36 @@ public abstract class WeakSet<T> implements Iterable<T> {
*/
public abstract Collection<T> values();
@Override
public Object[] toArray() {
return weakHashStorage.keySet().toArray();
}
// <T> is hiding the class declaration; it is needed to satisfy the interface
@SuppressWarnings("hiding")
@Override
public <T> T[] toArray(T[] a) {
return weakHashStorage.keySet().toArray(a);
}
@Override
public boolean containsAll(Collection<?> c) {
return weakHashStorage.keySet().containsAll(c);
}
@Override
public abstract boolean addAll(Collection<? extends T> c);
@Override
public abstract boolean retainAll(Collection<?> c);
@Override
public abstract boolean removeAll(Collection<?> c);
/**
* Returns a stream of the values of this collection.
* @return a stream of the values of this collection.
*/
@Override
public abstract Stream<T> stream();
}

View file

@ -0,0 +1,355 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import generic.json.Json;
import generic.test.AbstractGTest;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
public class ListenerSetTest {
@Test
public void testListenerNotification() {
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
SpyDummyListener spy = new SpyDummyListener();
listeners.add(spy);
String event = "Event";
listeners.invoke().workDone(event);
assertEquals(1, spy.getEvents().size());
assertEquals(event, spy.getEvents().get(0));
}
@Test
public void testBehavesLikeSet() {
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
SpyDummyListener l1 = new SpyDummyListener();
SpyDummyListener l2 = new SpyDummyListener();
listeners.add(l1);
listeners.add(l2);
listeners.invoke().workDone("EventA");
assertEquals(1, l1.getEvents().size());
assertEquals(1, l2.getEvents().size());
listeners.add(l1); // This had better not double fire
listeners.invoke().workDone("EventB");
assertEquals(2, l1.getEvents().size());
assertEquals(2, l2.getEvents().size());
}
@Test
public void testContinuesOnError() {
// disable the default error reporting to avoid polluting the console
Msg.setErrorLogger(new SpyErrorLogger());
Msg.setErrorDisplay(new SpyErrorDisplay());
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
SpyDummyListener l1 = new SpyDummyListener() {
@Override
public void workDone(String e) {
super.workDone(e);
throw new RuntimeException("It had better continue (1)");
}
};
listeners.add(l1);
SpyDummyListener l2 = new SpyDummyListener() {
@Override
public void workDone(String e) {
super.workDone(e);
throw new RuntimeException("It had better continue (2)");
}
};
listeners.add(l2);
listeners.invoke().workDone("Should see on both");
assertEquals("Should see on both", l1.getEvents().get(0));
assertEquals("Should see on both", l2.getEvents().get(0));
}
@Test
public void testWeaklyReferencesListeners() {
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
SpyDummyListener l1 = new SpyDummyListener();
listeners.add(l1);
listeners.invoke().workDone("EventA");
assertEquals("EventA", l1.get());
l1 = null; // Trash the only strong reference
AbstractGTest.waitForCondition(() -> {
System.gc();
return listeners.size() == 0;
});
}
@Test
public void testAddWhileNotifying() throws Exception {
//
// Test that any listeners added while notifying will not be notified and will not cause
// exceptions.
//
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
SpyErrorHandler spyErrorHandler = new SpyErrorHandler();
listeners.setErrorHandler(spyErrorHandler);
int n = 5;
List<SpyDummyListener> originalListeners = createLatchedListeners(n);
for (SpyDummyListener l : originalListeners) {
listeners.add(l);
}
// notify in another thread to not block the test thread
String event = "Event";
Thread notifyThread = new Thread(() -> {
listeners.invoke().workDone(event);
});
notifyThread.start();
List<SpyDummyListener> newListeners = new ArrayList<>();
for (int i = 0; i < n; i++) {
LatchedSpyListener blockedListener = AbstractGTest.waitFor(() -> activeListener);
activeListener = null;
// wait to ensure the listeners are being notified; mutate the listener list; tell the
// listener being notified to continue;
blockedListener.waitForStart();
SpyDummyListener l = new SpyDummyListener();
newListeners.add(l);
listeners.add(l);
blockedListener.proceed();
}
notifyThread.join(2000);
for (SpyDummyListener l : originalListeners) {
assertEquals(event, l.get());
}
for (SpyDummyListener newListener : newListeners) {
assertTrue(newListener.getEvents().isEmpty());
}
assertNull(spyErrorHandler.getException());
}
@Test
public void testRemoveWhileNotifying() throws Exception {
//
// Test that any listeners removed while notifying will are still notified and will not
// cause exceptions.
//
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
SpyErrorHandler spyErrorHandler = new SpyErrorHandler();
listeners.setErrorHandler(spyErrorHandler);
int n = 5;
List<SpyDummyListener> originalListeners = createLatchedListeners(n);
for (SpyDummyListener l : originalListeners) {
listeners.add(l);
}
// notify in another thread to not block the test thread
String event = "Event";
Thread notifyThread = new Thread(() -> {
listeners.invoke().workDone(event);
});
notifyThread.start();
for (int i = 0; i < n; i++) {
LatchedSpyListener blockedListener = AbstractGTest.waitFor(() -> activeListener);
activeListener = null;
// wait to ensure the listeners are being notified; mutate the listener list; tell the
// listener being notified to continue;
blockedListener.waitForStart();
listeners.remove(blockedListener);
blockedListener.proceed();
}
notifyThread.join(2000);
for (SpyDummyListener l : originalListeners) {
assertEquals(event, l.get());
}
assertNull(spyErrorHandler.getException());
}
@Test
public void testErrorReporting() {
ListenerSet<DummyListener> listeners = new ListenerSet<>(DummyListener.class, true);
SpyErrorHandler spyErrorHandler = new SpyErrorHandler();
listeners.setErrorHandler(spyErrorHandler);
listeners.add(new ExceptionalDummyListener());
String event = "Event";
listeners.invoke().workDone(event);
assertNotNull(spyErrorHandler.getException());
}
//=================================================================================================
// Thread-based Test Code
//=================================================================================================
// variables only used by the thread-based tests
private Throwable notificationException;
private LatchedSpyListener activeListener;
private List<SpyDummyListener> createLatchedListeners(int n) {
List<SpyDummyListener> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(new LatchedSpyListener());
}
return list;
}
private class LatchedSpyListener extends SpyDummyListener {
private CountDownLatch startedLatch = new CountDownLatch(1);
private CountDownLatch proceedLatch = new CountDownLatch(1);
void proceed() {
proceedLatch.countDown();
}
void waitForStart() throws InterruptedException {
assertTrue("Timed-out waiting for event notification",
startedLatch.await(2, TimeUnit.SECONDS));
}
@Override
public void workDone(String e) {
activeListener = this;
if (notificationException != null) {
return; // stop processing if the test fails
}
startedLatch.countDown();
super.workDone(e);
try {
if (!proceedLatch.await(2, TimeUnit.SECONDS)) {
notificationException =
new AssertException("Failed waiting to proceed in listener notificaiton");
}
}
catch (InterruptedException e1) {
notificationException =
new AssertException("Interrupted waiting to proceed in listener notification");
}
}
}
//=================================================================================================
// Dummy Listener
//=================================================================================================
public interface DummyListener {
void workDone(String e);
}
private int id = 0;
private class SpyDummyListener implements DummyListener {
private List<String> events = new ArrayList<>();
@SuppressWarnings("unused") // used by toString()
private String name;
SpyDummyListener() {
name = "Spy " + ++id;
}
@Override
public void workDone(String e) {
events.add(e);
}
String get() {
if (events.isEmpty()) {
return null;
}
return events.get(0);
}
List<String> getEvents() {
return events;
}
@Override
public String toString() {
return Json.toString(this);
}
}
//=================================================================================================
// Inner Classes
//=================================================================================================
private class ExceptionalDummyListener implements DummyListener {
@Override
public void workDone(String e) {
throw new RuntimeException("Fail!");
}
}
private class SpyErrorHandler implements ListenerErrorHandler {
private Throwable exception;
@Override
public void handleError(Object listener, Throwable t) {
if (exception == null) {
exception = t;
}
}
Throwable getException() {
return exception;
}
}
}

View file

@ -0,0 +1,52 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import generic.test.ConcurrentTestExceptionHandler;
/**
* A utility that allows tests to set the error handling behavior for all data structures that
* want flexible error handling. This class, in this package, allows us to override the factory
* that is used to create the error handlers for framework listener data structures. The standard
* behavior is to report errors to the the application log. Some clients wish to change this
* behavior in testing mode so that any errors will fail tests. Without overriding this behavior,
* unexpected errors during listener notification may be lost in the noise of the application log.
* <p>
* The {@link ConcurrentTestExceptionHandler} is the mechanism used to report errors. That class
* allows the testing framework to synchronize error reporting, including to fail tests when errors
* are encountered, in any thread. Tests can disable this failure behavior by calling
* {@link ConcurrentTestExceptionHandler#disable()}. Doing so allows tests to prevent test failure
* when encountering expected errors.
*/
public class TestDataStructureErrorHandlerInstaller {
public static void installConcurrentExceptionErrorHandler() {
DataStructureErrorHandlerFactory.listenerFactory = new ListenerErrorHandlerFactory() {
@Override
public ListenerErrorHandler createErrorHandler() {
return new ConcurrentErrorHandler();
}
};
}
private static class ConcurrentErrorHandler implements ListenerErrorHandler {
@Override
public void handleError(Object listener, Throwable t) {
ConcurrentTestExceptionHandler.handle(Thread.currentThread(), t);
}
}
}

View file

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