GP-2752: Remove TargetObject.addListener() and related

This commit is contained in:
Dan 2022-11-10 13:46:23 -05:00
parent c301dd2c89
commit 50c7217635
78 changed files with 384 additions and 1261 deletions

View file

@ -53,7 +53,10 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
}
@Override
public void consoleOutput(TargetObject console, Channel channel, byte[] out) {
public void consoleOutput(TargetObject object, Channel channel, byte[] out) {
if (object != targetConsole) {
return;
}
OutputStream os;
switch (channel) {
case STDOUT:
@ -79,6 +82,9 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
@AttributeCallback(TargetObject.DISPLAY_ATTRIBUTE_NAME)
public void displayChanged(TargetObject object, String display) {
if (object != targetConsole) {
return;
}
// TODO: Add setSubTitle(String) to InterpreterConsole
if (guiConsole == null) {
/**
@ -92,7 +98,10 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
}
@AttributeCallback(TargetInterpreter.PROMPT_ATTRIBUTE_NAME)
public void promptChanged(TargetObject interpreter, String prompt) {
public void promptChanged(TargetObject object, String prompt) {
if (object != targetConsole) {
return;
}
if (guiConsole == null) {
/**
* Can happen during init. setPrompt will get called immediately after guiConsole is
@ -105,10 +114,11 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
@Override
public void invalidated(TargetObject object, TargetObject branch, String reason) {
if (object != targetConsole) {
return;
}
Swing.runLater(() -> {
if (object == targetConsole) { // Redundant
consoleInvalidated();
}
consoleInvalidated();
});
}
}
@ -131,7 +141,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
T targetConsole) {
this.plugin = plugin;
this.targetConsole = targetConsole;
targetConsole.addListener(listener);
targetConsole.getModel().addModelListener(listener);
}
protected abstract CompletableFuture<Void> sendLine(String line);
@ -188,7 +198,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
.build();
guiConsole.addAction(actionPin);
DockingAction interruptAction = InterpreterInterruptAction.builder(plugin)
DockingAction interruptAction = InterpreterInterruptAction.builder(plugin)
.onAction(this::sendInterrupt)
.build();
guiConsole.addAction(interruptAction);

View file

@ -20,7 +20,6 @@ import java.awt.Color;
import java.awt.event.MouseEvent;
import java.io.PrintWriter;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
@ -795,9 +794,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
@Override
public void closeComponent() {
TargetObject targetObject = getRoot().getTargetObject();
if (targetObject != null) {
targetObject.removeListener(getListener());
DebuggerObjectModel model = getModel();
if (model != null) {
model.removeModelListener(getListener());
}
super.closeComponent();
}

View file

@ -21,7 +21,6 @@ import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetObject;
@ -212,21 +211,12 @@ public class DummyTargetObject implements TargetObject {
return attributes;
}
@Override
public void addListener(DebuggerModelListener l) {
}
@Override
public void removeListener(DebuggerModelListener l) {
}
public String getJoinedPath() {
return joinedPath;
}
@Override
public boolean isValid() {
// TODO Auto-generated method stub
return true;
}
@ -235,6 +225,7 @@ public class DummyTargetObject implements TargetObject {
return getName();
}
@Override
public Object getValue() {
return value;
}

View file

@ -404,7 +404,6 @@ public class ObjectTree implements ObjectPane {
DebuggerObjectsProvider provider = getProvider();
ObjectContainer oc = node.getContainer();
provider.deleteFromMap(oc);
oc.getTargetObject().removeListener(provider.getListener());
nodeMap.remove(path(node.getContainer()));
}

View file

@ -78,90 +78,40 @@ public class DebuggerModelServicePlugin extends Plugin
// Since used for naming, no ':' allowed.
public static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy.MM.dd-HH.mm.ss-z");
protected class ListenersForRemovalAndFocus {
protected final DebuggerObjectModel model;
protected TargetObject root;
protected TargetFocusScope focusScope;
protected class ForRemovalAndFocusListener extends AnnotatedDebuggerAttributeListener {
public ForRemovalAndFocusListener() {
super(MethodHandles.lookup());
}
protected DebuggerModelListener forRemoval = new DebuggerModelListener() {
@Override
public void invalidated(TargetObject object, TargetObject branch, String reason) {
synchronized (listenersByModel) {
ListenersForRemovalAndFocus listener = listenersByModel.remove(model);
if (listener == null) {
return;
}
assert listener.forRemoval == this;
dispose();
}
modelListeners.fire.elementRemoved(model);
@Override
public void invalidated(TargetObject object, TargetObject branch, String reason) {
if (!object.isRoot()) {
return;
}
DebuggerObjectModel model = object.getModel();
synchronized (models) {
models.remove(model);
}
model.removeModelListener(this);
modelListeners.fire.elementRemoved(model);
if (currentModel == model) {
activateModel(null);
}
};
protected DebuggerModelListener forFocus =
new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) {
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject object, TargetObject focused) {
fireFocusEvent(focused);
List<DebuggerModelServiceProxyPlugin> copy;
synchronized (proxies) {
copy = List.copyOf(proxies);
}
for (DebuggerModelServiceProxyPlugin proxy : copy) {
proxy.fireFocusEvent(focused);
}
}
};
protected ListenersForRemovalAndFocus(DebuggerObjectModel model) {
this.model = model;
}
protected CompletableFuture<Void> init() {
return model.fetchModelRoot().thenCompose(r -> {
synchronized (this) {
this.root = r;
}
boolean isInvalid = false;
try {
r.addListener(this.forRemoval);
}
catch (IllegalStateException e) {
isInvalid = true;
}
isInvalid |= !r.isValid();
if (isInvalid) {
forRemoval.invalidated(root, root, "Who knows?");
}
CompletableFuture<? extends TargetFocusScope> findSuitable =
DebugModelConventions.findSuitable(TargetFocusScope.class, r);
return findSuitable;
}).thenAccept(fs -> {
synchronized (this) {
this.focusScope = fs;
}
if (fs != null) {
fs.addListener(this.forFocus);
}
});
}
public void dispose() {
TargetObject savedRoot;
TargetFocusScope savedFocusScope;
synchronized (this) {
savedRoot = root;
savedFocusScope = focusScope;
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject object, TargetObject focused) {
// I don't think I care which scope
fireFocusEvent(focused);
List<DebuggerModelServiceProxyPlugin> copy;
synchronized (proxies) {
copy = List.copyOf(proxies);
}
if (savedRoot != null) {
savedRoot.removeListener(this.forRemoval);
}
if (savedFocusScope != null) {
savedFocusScope.removeListener(this.forFocus);
for (DebuggerModelServiceProxyPlugin proxy : copy) {
proxy.fireFocusEvent(focused);
}
}
}
};
protected class ListenerOnRecorders implements TraceRecorderListener {
@Override
@ -195,8 +145,11 @@ public class DebuggerModelServicePlugin extends Plugin
protected final Set<DebuggerModelFactory> factories = new HashSet<>();
// Keep strong references to my listeners, or they'll get torched
protected final Map<DebuggerObjectModel, ListenersForRemovalAndFocus> listenersByModel =
new LinkedHashMap<>();
//protected final Map<DebuggerObjectModel, ListenersForRemovalAndFocus> listenersByModel =
//new LinkedHashMap<>();
protected final Set<DebuggerObjectModel> models = new LinkedHashSet<>();
protected final ForRemovalAndFocusListener forRemovalAndFocusListener =
new ForRemovalAndFocusListener();
protected final Map<TargetObject, TraceRecorder> recordersByTarget = new WeakHashMap<>();
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners =
@ -262,8 +215,8 @@ public class DebuggerModelServicePlugin extends Plugin
@Override
public Set<DebuggerObjectModel> getModels() {
synchronized (listenersByModel) {
return Set.copyOf(listenersByModel.keySet());
synchronized (models) {
return Set.copyOf(models);
}
}
@ -286,20 +239,16 @@ public class DebuggerModelServicePlugin extends Plugin
@Override
public boolean addModel(DebuggerObjectModel model) {
Objects.requireNonNull(model);
synchronized (listenersByModel) {
if (listenersByModel.containsKey(model)) {
synchronized (models) {
if (models.contains(model)) {
return false;
}
ListenersForRemovalAndFocus listener = new ListenersForRemovalAndFocus(model);
listener.init().exceptionally(e -> {
Msg.error(this, "Could not add model " + model, e);
synchronized (listenersByModel) {
listenersByModel.remove(model);
listener.dispose();
}
return null;
});
listenersByModel.put(model, listener);
model.addModelListener(forRemovalAndFocusListener);
TargetObject root = model.getModelRoot();
if (!root.isValid()) {
forRemovalAndFocusListener.invalidated(root, root,
"Invalidated before or during add to service");
}
}
modelListeners.fire.elementAdded(model);
return true;
@ -307,14 +256,12 @@ public class DebuggerModelServicePlugin extends Plugin
@Override
public boolean removeModel(DebuggerObjectModel model) {
ListenersForRemovalAndFocus listener;
synchronized (listenersByModel) {
listener = listenersByModel.remove(model);
if (listener == null) {
model.removeModelListener(forRemovalAndFocusListener);
synchronized (models) {
if (!models.remove(model)) {
return false;
}
}
listener.dispose();
modelListeners.fire.elementRemoved(model);
return true;
}

View file

@ -1,251 +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.app.plugin.core.debug.service.model;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.app.plugin.core.debug.service.model.interfaces.AbstractRecorderMemory;
import ghidra.async.AsyncLazyMap;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebugModelConventions.AllRequiredAccess;
import ghidra.dbg.target.*;
import ghidra.program.model.address.*;
import ghidra.util.Msg;
import ghidra.util.TriConsumer;
import ghidra.util.datastruct.ListenerSet;
public class RecorderComposedMemory implements AbstractRecorderMemory {
private static final int BLOCK_SIZE = 4096;
private static final long BLOCK_MASK = -1L << 12;
protected final RecorderComposedMemory chain;
protected final NavigableMap<Address, TargetMemoryRegion> byMin = new TreeMap<>();
protected final Map<TargetMemoryRegion, TargetMemory> byRegion = new HashMap<>();
protected final AsyncLazyMap<TargetMemory, AllRequiredAccess> accessibilityByMemory =
new AsyncLazyMap<>(new HashMap<>(), this::fetchMemAccessibility) {
public AllRequiredAccess remove(TargetMemory key) {
AllRequiredAccess acc = super.remove(key);
if (acc != null) {
acc.removeChangeListener(getMemAccListeners().fire);
}
return acc;
}
};
protected CompletableFuture<AllRequiredAccess> fetchMemAccessibility(TargetMemory mem) {
return DebugModelConventions.trackAccessibility(mem).thenApply(acc -> {
acc.addChangeListener(getMemAccListeners().fire);
return acc;
});
}
/**
* Get accessible memory, as viewed in the trace
*
* @param pred an additional predicate applied via "AND" with accessibility
* @param memMapper target-to-trace mapping utility
* @return the computed set
*/
@Override
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper) {
synchronized (accessibilityByMemory) {
// TODO: Might accomplish by using listeners and tracking the accessible set
AddressSet accessible = new AddressSet();
for (Entry<TargetMemoryRegion, TargetMemory> ent : byRegion.entrySet()) {
TargetMemory mem = ent.getValue();
if (!pred.test(mem)) {
continue;
}
AllRequiredAccess acc = accessibilityByMemory.getCompletedMap().get(mem);
if (acc == null || !acc.getAllAccessibility()) {
continue;
}
AddressRange traceRange = memMapper.targetToTraceTruncated(ent.getKey().getRange());
if (traceRange == null) {
continue;
}
accessible.add(traceRange);
}
return accessible;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private final ListenerSet<TriConsumer<Boolean, Boolean, Void>> memAccListeners =
new ListenerSet(TriConsumer.class);
public RecorderComposedMemory(AbstractRecorderMemory memory) {
this.chain = (RecorderComposedMemory) memory;
}
protected TargetMemory getMemory(Address address, int length) {
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
if (floor == null) {
throw new IllegalArgumentException(
"address " + address + " is not in any known region");
}
Address max;
try {
max = address.addNoWrap(length - 1);
}
catch (AddressOverflowException e) {
throw new IllegalArgumentException("read extends beyond the address space");
}
if (!floor.getValue().getRange().contains(max)) {
throw new IllegalArgumentException("read extends beyond a single region");
}
return byRegion.get(floor.getValue());
}
@Override
public void addMemory(TargetMemory memory) {
// Do nothing
// This delegates by region, so need regions even if I have memory
}
@Override
public void addRegion(TargetMemoryRegion region, TargetMemory memory) {
synchronized (accessibilityByMemory) {
TargetMemory old = byRegion.put(region, memory);
assert old == null;
byMin.put(region.getRange().getMinAddress(), region);
accessibilityByMemory.get(memory).exceptionally(e -> {
e = AsyncUtils.unwrapThrowable(e);
Msg.error(this, "Could not track memory accessibility: " + e.getMessage());
return null;
});
}
}
@Override
public void removeMemory(TargetMemory invalid) {
// Do nothing
}
@Override
public boolean removeRegion(TargetObject invalid) {
if (!(invalid instanceof TargetMemoryRegion)) {
return false;
}
synchronized (accessibilityByMemory) {
TargetMemoryRegion invRegion = (TargetMemoryRegion) invalid;
TargetMemory old = byRegion.remove(invRegion);
assert old != null;
byMin.remove(invRegion.getRange().getMinAddress());
if (!old.isValid() || !byRegion.containsValue(old)) {
accessibilityByMemory.remove(old);
}
return true;
}
}
/*
protected AllRequiredAccess findChainedMemoryAccess(TargetMemoryRegion region) {
synchronized (accessibilityByMemory) {
TargetMemory mem = byRegion.get(region);
if (mem != null) {
return accessibilityByMemory.getCompletedMap().get(mem);
}
return chain == null ? null : chain.findChainedMemoryAccess(region);
}
}
*/
public Entry<Address, TargetMemoryRegion> findChainedFloor(Address address) {
synchronized (accessibilityByMemory) {
Entry<Address, TargetMemoryRegion> myFloor = byMin.floorEntry(address);
Entry<Address, TargetMemoryRegion> byChain =
chain == null ? null : chain.findChainedFloor(address);
if (byChain == null) {
return myFloor;
}
if (myFloor == null) {
return byChain;
}
int c = myFloor.getKey().compareTo(byChain.getKey());
if (c < 0) {
return byChain;
}
return myFloor;
}
}
protected AddressRange align(Address address, int length) {
AddressSpace space = address.getAddressSpace();
long offset = address.getOffset();
Address start = space.getAddress(offset & BLOCK_MASK);
Address end = space.getAddress(((offset + length - 1) & BLOCK_MASK) + BLOCK_SIZE - 1);
return new AddressRangeImpl(start, end);
}
protected AddressRange alignWithLimit(Address address, int length,
TargetMemoryRegion limit) {
return align(address, length).intersect(limit.getRange());
}
@Override
public AddressRange alignAndLimitToFloor(Address address, int length) {
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
if (floor == null) {
return null;
}
return alignWithLimit(address, length, floor.getValue());
}
public AddressRange alignWithOptionalLimit(Address address, int length,
TargetMemoryRegion limit) {
if (limit == null) {
return alignAndLimitToFloor(address, length);
}
return alignWithLimit(address, length, limit);
}
@Override
public CompletableFuture<byte[]> readMemory(Address address, int length) {
synchronized (accessibilityByMemory) {
TargetMemory mem = getMemory(address, length);
if (mem != null) {
return mem.readMemory(address, length);
}
return CompletableFuture.completedFuture(new byte[0]);
}
}
@Override
public CompletableFuture<Void> writeMemory(Address address, byte[] data) {
synchronized (accessibilityByMemory) {
TargetMemory mem = getMemory(address, data.length);
if (mem != null) {
return mem.writeMemory(address, data);
}
throw new IllegalArgumentException("read starts outside any address space");
}
}
public ListenerSet<TriConsumer<Boolean, Boolean, Void>> getMemAccListeners() {
return memAccListeners;
}
}

View file

@ -1,107 +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.app.plugin.core.debug.service.model;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncLazyMap;
import ghidra.async.AsyncLazyMap.KeyedFuture;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebugModelConventions.AllRequiredAccess;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetRegisterBank;
import ghidra.util.Msg;
import ghidra.util.TriConsumer;
public class RecorderComposedRegisterSet {
private DefaultTraceRecorder recorder;
protected final TriConsumer<Boolean, Boolean, Void> listenerRegAccChanged =
this::registerAccessibilityChanged;
protected void registerAccessibilityChanged(boolean old, boolean acc,
Void __) {
recorder.getListeners().fire.registerAccessibilityChanged(recorder);
}
protected final AsyncLazyMap<TargetRegisterBank, AllRequiredAccess> accessibilityByRegBank =
new AsyncLazyMap<>(new HashMap<>(), this::fetchRegAccessibility) {
public AllRequiredAccess remove(TargetRegisterBank key) {
AllRequiredAccess acc = super.remove(key);
if (acc != null) {
acc.removeChangeListener(listenerRegAccChanged);
}
return acc;
}
};
protected CompletableFuture<AllRequiredAccess> fetchRegAccessibility(
TargetRegisterBank bank) {
return DebugModelConventions.trackAccessibility(bank).thenApply(acc -> {
acc.addChangeListener(listenerRegAccChanged);
return acc;
});
}
public RecorderComposedRegisterSet(DefaultTraceRecorder recorder) {
this.recorder = recorder;
}
public void updateRegisters(TargetRegisterBank newRegs, TargetRegisterBank oldRegs) {
synchronized (accessibilityByRegBank) {
if (oldRegs != null) {
accessibilityByRegBank.remove(oldRegs);
}
accessibilityByRegBank.get(newRegs).exceptionally(e -> {
e = AsyncUtils.unwrapThrowable(e);
Msg.error(this, "Could not track register accessibility: " + e.getMessage());
return null;
});
}
}
public boolean checkRegistersRemoved(Map<Integer, TargetRegisterBank> regs,
TargetObject invalid) {
synchronized (accessibilityByRegBank) {
if (regs.values().remove(invalid)) {
accessibilityByRegBank.remove((TargetRegisterBank) invalid);
return true;
}
return false;
}
}
public boolean isRegisterBankAccessible(TargetRegisterBank bank) {
if (bank == null) {
return false;
}
synchronized (accessibilityByRegBank) {
KeyedFuture<?, AllRequiredAccess> future = accessibilityByRegBank.get(bank);
if (future == null) {
return false;
}
AllRequiredAccess acc = future.getNow(null);
if (acc == null) {
return false;
}
return acc.get();
}
}
}