blob: 26e938b70d98d3533fc58a4fb9dac3b8c4adb312 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007-2018 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.internal.debug.ui.model;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IExpressionManager;
import org.eclipse.debug.core.IExpressionsListener;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.commands.IDisconnectHandler;
import org.eclipse.debug.core.commands.IDropToFrameHandler;
import org.eclipse.debug.core.commands.IResumeHandler;
import org.eclipse.debug.core.commands.IStepIntoHandler;
import org.eclipse.debug.core.commands.IStepOverHandler;
import org.eclipse.debug.core.commands.IStepReturnHandler;
import org.eclipse.debug.core.commands.ISuspendHandler;
import org.eclipse.debug.core.commands.ITerminateHandler;
import org.eclipse.debug.core.model.IDebugModelProvider;
import org.eclipse.debug.core.model.IExpression;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IMemoryBlockExtension;
import org.eclipse.debug.core.model.IMemoryBlockRetrieval;
import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
import org.eclipse.debug.core.model.ISourceLocator;
import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentation;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentationFactory;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementCompareRequest;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementContentProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoRequest;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxyFactory;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelSelectionPolicy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelSelectionPolicyFactory;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputUpdate;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.debug.ui.IDebugView;
import org.eclipse.debug.ui.ISourcePresentation;
import org.eclipse.debug.ui.contexts.ISuspendTrigger;
import org.eclipse.debug.ui.contexts.ISuspendTriggerListener;
import org.eclipse.debug.ui.sourcelookup.CommonSourceNotFoundEditorInput;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.tcf.core.Command;
import org.eclipse.tcf.debug.ui.ITCFModel;
import org.eclipse.tcf.debug.ui.ITCFPresentationProvider;
import org.eclipse.tcf.debug.ui.ITCFSourceDisplay;
import org.eclipse.tcf.internal.debug.actions.TCFAction;
import org.eclipse.tcf.internal.debug.launch.TCFLaunchDelegate;
import org.eclipse.tcf.internal.debug.launch.TCFSourceLookupDirector;
import org.eclipse.tcf.internal.debug.launch.TCFSourceLookupParticipant;
import org.eclipse.tcf.internal.debug.model.ITCFConstants;
import org.eclipse.tcf.internal.debug.model.TCFContextState;
import org.eclipse.tcf.internal.debug.model.TCFLaunch;
import org.eclipse.tcf.internal.debug.model.TCFSourceRef;
import org.eclipse.tcf.internal.debug.ui.Activator;
import org.eclipse.tcf.internal.debug.ui.adapters.TCFNodePropertySource;
import org.eclipse.tcf.internal.debug.ui.commands.BackIntoCommand;
import org.eclipse.tcf.internal.debug.ui.commands.BackOverCommand;
import org.eclipse.tcf.internal.debug.ui.commands.BackResumeCommand;
import org.eclipse.tcf.internal.debug.ui.commands.BackReturnCommand;
import org.eclipse.tcf.internal.debug.ui.commands.DisconnectCommand;
import org.eclipse.tcf.internal.debug.ui.commands.DropToFrameCommand;
import org.eclipse.tcf.internal.debug.ui.commands.ResumeCommand;
import org.eclipse.tcf.internal.debug.ui.commands.StepIntoCommand;
import org.eclipse.tcf.internal.debug.ui.commands.StepOverCommand;
import org.eclipse.tcf.internal.debug.ui.commands.StepReturnCommand;
import org.eclipse.tcf.internal.debug.ui.commands.SuspendCommand;
import org.eclipse.tcf.internal.debug.ui.commands.TerminateCommand;
import org.eclipse.tcf.internal.debug.ui.preferences.TCFPreferences;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IErrorReport;
import org.eclipse.tcf.protocol.IService;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IDisassembly;
import org.eclipse.tcf.services.ILineNumbers;
import org.eclipse.tcf.services.IMemory;
import org.eclipse.tcf.services.IMemoryMap;
import org.eclipse.tcf.services.IPathMap;
import org.eclipse.tcf.services.IProcesses;
import org.eclipse.tcf.services.IProfiler;
import org.eclipse.tcf.services.IRegisters;
import org.eclipse.tcf.services.IRunControl;
import org.eclipse.tcf.services.IStackTrace;
import org.eclipse.tcf.services.ISymbols;
import org.eclipse.tcf.util.TCFDataCache;
import org.eclipse.tcf.util.TCFTask;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
/**
* TCFModel represents remote target state as it is known to host.
* The main job of the model is caching remote data,
* keeping the cache in a coherent state,
* and feeding UI with up-to-date data.
*/
public class TCFModel implements ITCFModel, IElementContentProvider, IElementLabelProvider, IViewerInputProvider,
IModelProxyFactory, IColumnPresentationFactory, ITCFSourceDisplay, ISuspendTrigger, IElementMementoProvider {
/** The id of the expression hover presentation context */
public static final String ID_EXPRESSION_HOVER = Activator.PLUGIN_ID + ".expression_hover";
/** The id of a pinned view description presentation context */
public static final String ID_PINNED_VIEW = Activator.PLUGIN_ID + ".pinned_view";
/** Selection reason in the Debug view - context added */
public static final String SELECT_ADDED = "Added";
/** Selection reason in the Debug view - initial selection upon launch */
public static final String SELECT_INITIAL = "Launch";
public static final int
UPDATE_POLICY_AUTOMATIC = 0,
UPDATE_POLICY_MANUAL = 1,
UPDATE_POLICY_BREAKPOINT = 2;
/**
* A dummy editor input to open the disassembly view as editor.
*/
public static class DisassemblyEditorInput implements IEditorInput {
final static String EDITOR_ID = "org.eclipse.cdt.dsf.ui.disassembly";
final static DisassemblyEditorInput INSTANCE = new DisassemblyEditorInput();
@SuppressWarnings("rawtypes")
public Object getAdapter(Class adapter) {
return null;
}
public boolean exists() {
return false;
}
public ImageDescriptor getImageDescriptor() {
return null;
}
public String getName() {
return "Disassembly";
}
public IPersistableElement getPersistable() {
return null;
}
public String getToolTipText() {
return "Disassembly";
}
}
private static final int
DISPLAY_SOURCE_ON_REFRESH = 0,
DISPLAY_SOURCE_ON_SUSPEND = 1,
DISPLAY_SOURCE_ON_STEP_MODE = 2;
private final TCFLaunch launch;
private final Display display;
private final IExpressionManager expr_manager;
private final TCFAnnotationManager annotation_manager;
private InitialSelection initial_selection;
private InitialSuspendTrigger initial_suspend_trigger;
private final List<ISuspendTriggerListener> suspend_trigger_listeners =
new LinkedList<ISuspendTriggerListener>();
final List<ITCFPresentationProvider> view_request_listeners;
private final Map<IWorkbenchPage,Object> display_source_generation =
new WeakHashMap<IWorkbenchPage,Object>();
private int suspend_trigger_generation;
private int auto_disconnect_generation;
private boolean reverse_debug_enabled;
// Debugger preferences:
private long min_view_updates_interval;
private boolean view_updates_throttle_enabled;
private boolean channel_throttle_enabled;
private boolean wait_for_pc_update_after_step;
private boolean wait_for_views_update_after_step;
private boolean delay_stack_update_until_last_step;
private boolean stack_frames_limit_enabled;
private int stack_frames_limit_value;
private boolean show_function_arg_names;
private boolean show_function_arg_values;
private boolean delay_children_list_updates;
private boolean auto_children_list_updates;
private boolean show_full_error_reports;
private boolean hover_while_running;
private boolean qualified_type_names_enabled;
private boolean filter_variants_by_discriminant;
private final Map<String,String> action_results = new HashMap<String,String>();
private final HashMap<String,TCFAction> active_actions = new HashMap<String,TCFAction>();
private final List<TCFModelProxy> model_proxies = new ArrayList<TCFModelProxy>();
private final Map<String,TCFNode> id2node = new HashMap<String,TCFNode>();
private final Map<Class<?>,Object> adapters = new HashMap<Class<?>,Object>();
private final List<TCFMemoryBlock> mem_blocks = new ArrayList<TCFMemoryBlock>();
private final Map<String,IMemoryBlockRetrievalExtension> mem_retrieval = new HashMap<String,IMemoryBlockRetrievalExtension>();
private final IMemoryBlockRetrievalExtension mem_not_supported = new IMemoryBlockRetrievalExtension() {
@Override
public boolean supportsStorageRetrieval() {
return false;
}
@Override
public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException {
return null;
}
@Override
public IMemoryBlockExtension getExtendedMemoryBlock(String expression, Object context) throws DebugException {
return null;
}
};
private class MemoryBlocksUpdate implements Runnable {
final Set<String> changeset = new HashSet<String>();
final Set<String> suspended = new HashSet<String>();
MemoryBlocksUpdate() {
mem_blocks_update = this;
Protocol.invokeLater(this);
if (wait_for_views_update_after_step) {
launch.addPendingClient(this);
}
}
void add(String id, boolean suspended) {
changeset.add(id);
if (suspended) this.suspended.add(id);
}
public void run() {
assert mem_blocks_update == this;
if (channel.getState() == IChannel.STATE_OPEN && mem_blocks.size() > 0) {
// Map changed contexts to memory nodes, and then to memory block objects
Set<TCFMemoryBlock> set = new HashSet<TCFMemoryBlock>();
for (String id : changeset) {
if (!createNode(id, this)) return;
TCFNode node = id2node.get(id);
if (node instanceof TCFNodeExecContext) {
TCFDataCache<TCFNodeExecContext> c = ((TCFNodeExecContext)node).getMemoryNode();
if (!c.validate(this)) return;
node = c.getData();
if (node == null) continue;
for (TCFMemoryBlock b : mem_blocks) {
if (b.getMemoryID().equals(node.id)) {
if (suspended.contains(id)) suspended.add(node.id);
set.add(b);
}
}
}
}
for (TCFMemoryBlock b : set) b.onMemoryChanged(suspended.contains(b.getMemoryID()));
}
launch.removePendingClient(this);
mem_blocks_update = null;
}
}
private MemoryBlocksUpdate mem_blocks_update;
private final Map<String,String> cast_to_type_map = new HashMap<String,String>();
private final Map<String,Object> context_map = new HashMap<String,Object>();
private final Map<IWorkbenchPart,TCFNode> pins = new HashMap<IWorkbenchPart,TCFNode>();
private final Map<IWorkbenchPart,TCFSnapshot> locks = new HashMap<IWorkbenchPart,TCFSnapshot>();
private final Map<IWorkbenchPart,Integer> lock_policy = new HashMap<IWorkbenchPart,Integer>();
private final Map<String,TCFConsole> process_consoles = new HashMap<String,TCFConsole>();
private final List<TCFConsole> debug_consoles = new ArrayList<TCFConsole>();
private TCFConsole dprintf_console;
private static final Map<ILaunchConfiguration,IEditorInput> editor_not_found =
new HashMap<ILaunchConfiguration,IEditorInput>();
private final IModelSelectionPolicyFactory model_selection_factory = new IModelSelectionPolicyFactory() {
public IModelSelectionPolicy createModelSelectionPolicyAdapter(
Object element, IPresentationContext context) {
return selection_policy;
}
};
private final IModelSelectionPolicy selection_policy;
private IChannel channel;
private TCFNodeLaunch launch_node;
private boolean disposed;
private final IMemory.MemoryListener mem_listener = new IMemory.MemoryListener() {
public void contextAdded(IMemory.MemoryContext[] contexts) {
for (IMemory.MemoryContext ctx : contexts) {
String id = ctx.getParentID();
if (id == null) {
launch_node.onContextAdded(ctx);
}
else {
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextAdded(ctx);
}
}
}
launch_node.onAnyContextAddedOrRemoved();
}
public void contextChanged(IMemory.MemoryContext[] contexts) {
for (IMemory.MemoryContext ctx : contexts) {
String id = ctx.getID();
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextChanged(ctx);
}
onMemoryChanged(id, true, false, false);
}
}
public void contextRemoved(final String[] context_ids) {
onContextRemoved(context_ids);
}
public void memoryChanged(String id, Number[] addr, long[] size) {
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onMemoryChanged(addr, size);
}
onMemoryChanged(id, true, false, false);
}
};
private final IRunControl.RunControlListener run_listener = new IRunControl.RunControlListenerV1() {
public void containerResumed(String[] context_ids) {
for (String id : context_ids) {
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContainerResumed();
}
}
updateAnnotations(null);
}
public void containerSuspended(String context, String pc, String reason,
Map<String,Object> params, String[] suspended_ids) {
boolean func_call = false;
if (params != null) {
Boolean b = (Boolean)params.get(IRunControl.STATE_FUNC_CALL);
func_call = b != null && b.booleanValue();
}
int action_cnt = 0;
for (String id : suspended_ids) {
TCFNode node = getNode(id);
action_results.remove(id);
if (active_actions.get(id) != null) action_cnt++;
if (!id.equals(context) && node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContainerSuspended(func_call);
}
onMemoryChanged(id, true, true, false);
}
TCFNode node = getNode(context);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextSuspended(pc, reason, params, func_call);
}
launch_node.onAnyContextSuspendedOrChanged();
if (!func_call && action_cnt == 0) {
setDebugViewSelection(node, reason);
updateAnnotations(null);
TCFNodePropertySource.refresh(node);
}
action_results.remove(context);
}
public void contextAdded(IRunControl.RunControlContext[] contexts) {
for (IRunControl.RunControlContext ctx : contexts) {
String id = ctx.getParentID();
if (id == null) {
launch_node.onContextAdded(ctx);
}
else {
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextAdded(ctx);
}
}
context_map.put(ctx.getID(), ctx);
}
launch_node.onAnyContextAddedOrRemoved();
}
public void contextChanged(IRunControl.RunControlContext[] contexts) {
for (IRunControl.RunControlContext ctx : contexts) {
String id = ctx.getID();
context_map.put(id, ctx);
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextChanged(ctx);
}
onMemoryChanged(id, true, false, false);
if (active_actions.get(id) == null) {
TCFNodePropertySource.refresh(node);
}
}
launch_node.onAnyContextSuspendedOrChanged();
}
public void contextException(String context, String msg) {
TCFNode node = getNode(context);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextException(msg);
}
}
public void contextRemoved(final String[] context_ids) {
onContextRemoved(context_ids);
}
public void contextResumed(String id) {
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextResumed();
}
updateAnnotations(null);
}
public void contextSuspended(String id, String pc, String reason, Map<String,Object> params) {
boolean func_call = false;
if (params != null) {
Boolean b = (Boolean)params.get(IRunControl.STATE_FUNC_CALL);
func_call = b != null && b.booleanValue();
}
TCFNode node = getNode(id);
action_results.remove(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextSuspended(pc, reason, params, func_call);
}
launch_node.onAnyContextSuspendedOrChanged();
if (!func_call && active_actions.get(id) == null) {
setDebugViewSelection(node, reason);
updateAnnotations(null);
TCFNodePropertySource.refresh(node);
}
onMemoryChanged(id, true, true, false);
}
public void contextStateChanged(String id) {
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextStateChanged();
}
}
};
private final IMemoryMap.MemoryMapListener mmap_listener = new IMemoryMap.MemoryMapListener() {
public void changed(String id) {
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
TCFNodeExecContext exe = (TCFNodeExecContext)node;
exe.onMemoryMapChanged();
}
onMemoryChanged(id, true, false, true);
refreshSourceView();
}
};
private final IPathMap.PathMapListener pmap_listener = new IPathMap.PathMapListener() {
public void changed() {
refreshSourceView();
}
};
private final IRegisters.RegistersListener reg_listener = new IRegisters.RegistersListener() {
public void contextChanged() {
LinkedList<TCFNode> regs = new LinkedList<TCFNode>();
for (TCFNode node : id2node.values()) {
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onRegistersChanged();
}
if (node instanceof TCFNodeRegister) {
assert !node.isDisposed();
regs.add(node);
}
}
// Must dispose all register nodes, because a register
// can move to a different parent
for (TCFNode node : regs) {
if (!node.isDisposed()) node.dispose();
}
}
public void registerChanged(final String id) {
/*
* We need to propagate register changes to parent node,
* because, for example, stack frames might need to be updated.
* For that we need to know parent of the register.
* It means we have to create register node if it does not exist yet.
*/
// TODO: async handling of registerChanged event - potential data coherency issue
Runnable done = new Runnable() {
@Override
public void run() {
TCFNode node = getNode(id);
if (node instanceof TCFNodeRegister) {
((TCFNodeRegister)node).onValueChanged();
}
}
};
if (createNode(id, done)) done.run();
}
};
private final IProcesses.ProcessesListener prs_listener = new IProcesses.ProcessesListener() {
public void exited(String process_id, int exit_code) {
IProcesses.ProcessContext prs = launch.getProcessContext();
if (prs != null && process_id.equals(prs.getID())) onContextOrProcessRemoved();
}
};
private final IExpressionsListener expressions_listener = new IExpressionsListener() {
int generation;
public void expressionsAdded(IExpression[] expressions) {
expressionsRemoved(expressions);
}
public void expressionsChanged(IExpression[] expressions) {
expressionsRemoved(expressions);
}
public void expressionsRemoved(IExpression[] expressions) {
final int g = ++generation;
Protocol.invokeLater(new Runnable() {
public void run() {
if (g != generation) return;
for (TCFNode n : id2node.values()) {
if (n instanceof TCFNodeExecContext) {
((TCFNodeExecContext)n).onExpressionAddedOrRemoved();
}
}
for (TCFModelProxy p : model_proxies) {
String id = p.getPresentationContext().getId();
if (IDebugUIConstants.ID_EXPRESSION_VIEW.equals(id)) {
Object o = p.getInput();
if (o instanceof TCFNode) {
TCFNode n = (TCFNode)o;
if (n.model == TCFModel.this) p.addDelta(n, IModelDelta.CONTENT);
}
}
}
}
});
}
};
private final TCFLaunch.ActionsListener actions_listener = new TCFLaunch.ActionsListener() {
public void onContextActionStart(TCFAction action) {
final String id = action.getContextID();
active_actions.put(id, action);
updateAnnotations(null);
}
public void onContextActionResult(String id, String reason) {
if (reason == null) action_results.remove(id);
else action_results.put(id, reason);
}
public void onContextActionDone(final TCFAction action) {
Runnable r = new Runnable() {
@Override
public void run() {
TCFContextState state_data = null;
String id = action.getContextID();
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
TCFDataCache<TCFContextState> state_cache = ((TCFNodeExecContext)node).getState();
if (!state_cache.validate(this)) return;
state_data = state_cache.getData();
}
if (active_actions.get(id) == action) {
active_actions.remove(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextActionDone();
if (state_data != null && state_data.is_suspended) {
String reason = action_results.get(id);
if (reason == null) reason = state_data.suspend_reason;
setDebugViewSelection(id2node.get(id), reason);
}
}
for (TCFModelProxy p : model_proxies) p.post();
updateAnnotations(null);
TCFNodePropertySource.refresh(node);
}
launch.removePendingClient(this);
}
};
launch.addPendingClient(r);
Protocol.invokeLater(r);
}
};
private final IDebugModelProvider debug_model_provider = new IDebugModelProvider() {
public String[] getModelIdentifiers() {
return new String[] { ITCFConstants.ID_TCF_DEBUG_MODEL };
}
};
private class InitialSelection extends InitialRunnable {
final TCFModelProxy proxy;
boolean launch_selected;
boolean done;
InitialSelection(TCFModelProxy proxy) {
this.proxy = proxy;
initial_selection = this;
}
public void run() {
if (done) return;
if (initial_selection != this) return;
if (launch_node != null && !proxy.isDisposed()) {
ArrayList<TCFNodeExecContext> nodes = new ArrayList<TCFNodeExecContext>();
if (!searchSuspendedThreads(launch_node.getFilteredChildren(), nodes)) return;
if (nodes.size() == 0) {
if (!launch_selected) {
setDebugViewSelectionForProxy(proxy, launch_node, SELECT_INITIAL);
launch_selected = true;
}
// No usable selection. Re-run when a context is suspended.
return;
}
else if (nodes.size() == 1) {
TCFNodeExecContext n = nodes.get(0);
setDebugViewSelectionForProxy(proxy, n, SELECT_INITIAL);
}
else {
boolean node_selected = false;
for (TCFNodeExecContext n : nodes) {
String reason = n.getState().getData().suspend_reason;
node_selected |= setDebugViewSelectionForProxy(proxy, n, reason);
}
if (!node_selected) {
// bug 420740: No node was selected - select the first one
setDebugViewSelectionForProxy(proxy, nodes.get(0), SELECT_INITIAL);
}
}
}
initial_selection = null;
done = true;
}
};
private class InitialSuspendTrigger extends InitialRunnable {
public InitialSuspendTrigger() {
initial_suspend_trigger = this;
}
@Override
public void run() {
if (launch_node == null || initial_suspend_trigger != this) return;
ArrayList<TCFNodeExecContext> nodes = new ArrayList<TCFNodeExecContext>();
if (!searchSuspendedThreads(launch_node.getFilteredChildren(), nodes)) return;
if (nodes.size() > 0) runSuspendTrigger(nodes.get(0));
}
};
private abstract class InitialRunnable implements Runnable {
protected boolean searchSuspendedThreads(TCFChildren c, ArrayList<TCFNodeExecContext> nodes) {
if (!c.validate(this)) return false;
for (TCFNode n : c.toArray()) {
if (!searchSuspendedThreads((TCFNodeExecContext)n, nodes)) return false;
}
return true;
}
private boolean searchSuspendedThreads(TCFNodeExecContext n, ArrayList<TCFNodeExecContext> nodes) {
TCFDataCache<IRunControl.RunControlContext> run_context = n.getRunContext();
if (!run_context.validate(this)) return false;
IRunControl.RunControlContext ctx = run_context.getData();
if (ctx != null && ctx.hasState()) {
TCFDataCache<TCFContextState> state = n.getState();
if (!state.validate(this)) return false;
TCFContextState s = state.getData();
if (s != null && s.is_suspended) nodes.add(n);
return true;
}
return searchSuspendedThreads(n.getChildren(), nodes);
}
}
private final IPreferenceStore prefs_store = TCFPreferences.getPreferenceStore();
private final IPropertyChangeListener prefs_listener = new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
launch.setContextActionsInterval(prefs_store.getLong(TCFPreferences.PREF_MIN_STEP_INTERVAL));
min_view_updates_interval = prefs_store.getLong(TCFPreferences.PREF_MIN_UPDATE_INTERVAL);
view_updates_throttle_enabled = prefs_store.getBoolean(TCFPreferences.PREF_VIEW_UPDATES_THROTTLE);
channel_throttle_enabled = prefs_store.getBoolean(TCFPreferences.PREF_TARGET_TRAFFIC_THROTTLE);
wait_for_pc_update_after_step = prefs_store.getBoolean(TCFPreferences.PREF_WAIT_FOR_PC_UPDATE_AFTER_STEP);
wait_for_views_update_after_step = prefs_store.getBoolean(TCFPreferences.PREF_WAIT_FOR_VIEWS_UPDATE_AFTER_STEP);
delay_stack_update_until_last_step = prefs_store.getBoolean(TCFPreferences.PREF_DELAY_STACK_UPDATE_UNTIL_LAST_STEP);
stack_frames_limit_enabled = prefs_store.getBoolean(TCFPreferences.PREF_STACK_FRAME_LIMIT_ENABLED);
stack_frames_limit_value = prefs_store.getInt(TCFPreferences.PREF_STACK_FRAME_LIMIT_VALUE);
show_function_arg_names = prefs_store.getBoolean(TCFPreferences.PREF_STACK_FRAME_ARG_NAMES);
show_function_arg_values = prefs_store.getBoolean(TCFPreferences.PREF_STACK_FRAME_ARG_VALUES);
auto_children_list_updates = prefs_store.getBoolean(TCFPreferences.PREF_AUTO_CHILDREN_LIST_UPDATES);
delay_children_list_updates = prefs_store.getBoolean(TCFPreferences.PREF_DELAY_CHILDREN_LIST_UPDATES);
show_full_error_reports = prefs_store.getBoolean(TCFPreferences.PREF_FULL_ERROR_REPORTS);
hover_while_running = prefs_store.getBoolean(TCFPreferences.PREF_HOVER_WHILE_RUNNING);
qualified_type_names_enabled = prefs_store.getBoolean(TCFPreferences.PREF_SHOW_QUALIFIED_TYPE_NAMES);
filter_variants_by_discriminant = prefs_store.getBoolean(TCFPreferences.PREF_FILTER_VARIANTS_BY_DISCRIMINANT);
final boolean affectsExpressionsOnly = event != null && (
TCFPreferences.PREF_SHOW_QUALIFIED_TYPE_NAMES.equals(event.getProperty()) ||
TCFPreferences.PREF_FILTER_VARIANTS_BY_DISCRIMINANT.equals(event.getProperty()));
Protocol.invokeLater(new Runnable() {
public void run() {
for (TCFNode n : id2node.values()) {
if (n instanceof TCFNodeExecContext && !affectsExpressionsOnly) {
((TCFNodeExecContext)n).onPreferencesChanged();
}
else if (n instanceof TCFNodeExpression && affectsExpressionsOnly) {
((TCFNodeExpression)n).onPreferencesChanged();
}
}
}
});
}
};
private volatile boolean instruction_stepping_enabled;
TCFModel(final TCFLaunch launch) {
this.launch = launch;
display = PlatformUI.getWorkbench().getDisplay();
selection_policy = new TCFModelSelectionPolicy(this);
adapters.put(ILaunch.class, launch);
adapters.put(IModelSelectionPolicy.class, selection_policy);
adapters.put(IModelSelectionPolicyFactory.class, model_selection_factory);
adapters.put(IDebugModelProvider.class, debug_model_provider);
adapters.put(ISuspendHandler.class, new SuspendCommand(this));
adapters.put(IResumeHandler.class, new ResumeCommand(this));
adapters.put(BackResumeCommand.class, new BackResumeCommand(this));
adapters.put(ITerminateHandler.class, new TerminateCommand(this));
adapters.put(IDisconnectHandler.class, new DisconnectCommand(this));
adapters.put(IStepIntoHandler.class, new StepIntoCommand(this));
adapters.put(IStepOverHandler.class, new StepOverCommand(this));
adapters.put(IStepReturnHandler.class, new StepReturnCommand(this));
adapters.put(BackIntoCommand.class, new BackIntoCommand(this));
adapters.put(BackOverCommand.class, new BackOverCommand(this));
adapters.put(BackReturnCommand.class, new BackReturnCommand(this));
adapters.put(IDropToFrameHandler.class, new DropToFrameCommand(this));
expr_manager = DebugPlugin.getDefault().getExpressionManager();
expr_manager.addExpressionListener(expressions_listener);
annotation_manager = Activator.getAnnotationManager();
launch.addActionsListener(actions_listener);
prefs_listener.propertyChange(null);
prefs_store.addPropertyChangeListener(prefs_listener);
List<ITCFPresentationProvider> l = new ArrayList<ITCFPresentationProvider>();
for (ITCFPresentationProvider p : TCFPresentationProvider.getPresentationProviders()) {
try {
if (p.onModelCreated(this)) l.add(p);
}
catch (Throwable x) {
Activator.log("Unhandled exception in a presentation provider", x);
}
}
view_request_listeners = l.size() > 0 ? l : null;
TCFMemoryBlock.onModelCreated(this);
}
/**
* Add an adapter for given type.
*
* @param adapterType the type the adapter implements
* @param adapter the adapter implementing <code>adapterType</code>
*/
public void setAdapter(Class<?> adapterType, Object adapter) {
synchronized (adapters) {
adapters.put(adapterType, adapter);
}
}
@SuppressWarnings("rawtypes")
public Object getAdapter(final Class adapter, final TCFNode node) {
synchronized (adapters) {
Object o = adapters.get(adapter);
if (o != null) return o;
}
if (adapter == IMemoryBlockRetrieval.class || adapter == IMemoryBlockRetrievalExtension.class) {
return new TCFTask<Object>() {
public void run() {
Object o = null;
TCFDataCache<TCFNodeExecContext> cache = searchMemoryContext(node);
if (cache != null) {
if (!cache.validate(this)) return;
if (cache.getData() != null) {
TCFNodeExecContext ctx = cache.getData();
if (!ctx.getMemoryContext().validate(this)) return;
if (ctx.getMemoryContext().getError() == null) {
o = getMemoryBlockRetrieval(ctx.id);
}
}
}
if (o == null) o = mem_not_supported;
assert adapter.isInstance(o);
done(o);
}
}.getE();
}
return null;
}
TCFMemoryBlock getMemoryBlock(String id, String expression, long length) {
assert Protocol.isDispatchThread();
IMemoryBlockRetrievalExtension r = getMemoryBlockRetrieval(id);
TCFMemoryBlock b = new TCFMemoryBlock(this, r, id, expression, length);
mem_blocks.add(b);
return b;
}
private IMemoryBlockRetrievalExtension getMemoryBlockRetrieval(final String id) {
/*
* Note: platform uses MemoryBlockRetrieval objects to link memory blocks with selection in the Debug view.
* We need to maintain 1 to 1 mapping between memory context IDs and MemoryBlockRetrieval objects.
*/
assert Protocol.isDispatchThread();
IMemoryBlockRetrievalExtension r = mem_retrieval.get(id);
if (r == null) {
r = new IMemoryBlockRetrievalExtension() {
@Override
public IMemoryBlockExtension getExtendedMemoryBlock(final String expression, Object context) throws DebugException {
final IMemoryBlockRetrieval r = this;
return new TCFDebugTask<IMemoryBlockExtension>() {
@Override
public void run() {
TCFMemoryBlock b = new TCFMemoryBlock(TCFModel.this, r, id, expression, -1);
mem_blocks.add(b);
done(b);
}
}.getD();
}
@Override
public IMemoryBlock getMemoryBlock(final long address, final long length) throws DebugException {
final IMemoryBlockRetrieval r = this;
return new TCFDebugTask<IMemoryBlock>() {
@Override
public void run() {
TCFMemoryBlock b = new TCFMemoryBlock(TCFModel.this, r, id, "0x" + Long.toHexString(address), length);
mem_blocks.add(b);
done(b);
}
}.getD();
}
@Override
public boolean supportsStorageRetrieval() {
return true;
}
};
mem_retrieval.put(id, r);
}
return r;
}
void asyncExec(Runnable r) {
synchronized (Device.class) {
if (display.isDisposed()) return;
display.asyncExec(r);
}
}
void onConnected() {
assert Protocol.isDispatchThread();
assert launch_node == null;
channel = launch.getChannel();
launch_node = new TCFNodeLaunch(this);
IMemory mem = launch.getService(IMemory.class);
if (mem != null) mem.addListener(mem_listener);
IRunControl run = launch.getService(IRunControl.class);
if (run != null) run.addListener(run_listener);
IMemoryMap mmap = launch.getService(IMemoryMap.class);
if (mmap != null) mmap.addListener(mmap_listener);
IPathMap pmap = launch.getService(IPathMap.class);
if (pmap != null) pmap.addListener(pmap_listener);
IRegisters reg = launch.getService(IRegisters.class);
if (reg != null) reg.addListener(reg_listener);
IProcesses prs = launch.getService(IProcesses.class);
if (prs != null) prs.addListener(prs_listener);
launchChanged();
Protocol.invokeLater(new InitialSuspendTrigger());
for (TCFModelProxy p : model_proxies) {
String id = p.getPresentationContext().getId();
if (IDebugUIConstants.ID_DEBUG_VIEW.equals(id)) {
Protocol.invokeLater(new InitialSelection(p));
}
}
if (launch.isProcessExited()) onContextOrProcessRemoved();
for (TCFConsole c : process_consoles.values()) c.onModelConnected();
}
void onDisconnected() {
assert Protocol.isDispatchThread();
if (locks.size() > 0) {
TCFSnapshot[] arr = locks.values().toArray(new TCFSnapshot[locks.size()]);
locks.clear();
for (TCFSnapshot s : arr) s.dispose();
}
if (launch.getChannel() != null) {
IMemory mem = launch.getService(IMemory.class);
if (mem != null) mem.removeListener(mem_listener);
IRunControl run = launch.getService(IRunControl.class);
if (run != null) run.removeListener(run_listener);
IMemoryMap mmap = launch.getService(IMemoryMap.class);
if (mmap != null) mmap.removeListener(mmap_listener);
IPathMap pmap = launch.getService(IPathMap.class);
if (pmap != null) pmap.removeListener(pmap_listener);
IRegisters reg = launch.getService(IRegisters.class);
if (reg != null) reg.removeListener(reg_listener);
IProcesses prs = launch.getService(IProcesses.class);
if (prs != null) prs.removeListener(prs_listener);
}
if (launch_node != null) {
launch_node.dispose();
launch_node = null;
}
// Dispose memory monitors
TCFMemoryBlock.onModelDisconnected(this);
mem_retrieval.clear();
mem_blocks.clear();
// Refresh the Debug view - cannot be done through ModelProxy since it is disposed
refreshLaunchView();
assert id2node.size() == 0;
}
void onProcessOutput(String ctx_id, int stream_id, byte[] data) {
if (ctx_id != null) {
TCFConsole c = process_consoles.get(ctx_id);
if (c == null) {
int type = TCFConsole.TYPE_UART_TERMINAL;
IProcesses.ProcessContext prs = launch.getProcessContext();
if (prs != null && ctx_id != null && ctx_id.equals(prs.getID())) {
type = TCFConsole.TYPE_PROCESS_TERMINAL;
boolean use_terminal = true;
try {
use_terminal = launch.getLaunchConfiguration().getAttribute(
TCFLaunchDelegate.ATTR_USE_TERMINAL, true);
}
catch (CoreException e) {
}
if (!use_terminal) type = TCFConsole.TYPE_PROCESS_CONSOLE;
}
c = new TCFConsole(this, type, ctx_id);
process_consoles.put(ctx_id, c);
if (launch_node != null) c.onModelConnected();
}
c.write(stream_id, data);
}
else {
if (dprintf_console == null) {
dprintf_console = new TCFConsole(TCFModel.this, TCFConsole.TYPE_DPRINTF, null);
}
dprintf_console.write(stream_id, data);
}
}
void onProcessStreamError(String process_id, int stream_id, Exception x, int lost_size) {
if (channel == null || channel.getState() == IChannel.STATE_CLOSED) return;
StringBuffer bf = new StringBuffer();
bf.append("Debugger console IO error");
if (process_id != null) {
bf.append(". Process ID ");
bf.append(process_id);
}
bf.append(". Stream ");
bf.append(stream_id);
if (lost_size > 0) {
bf.append(". Lost data size ");
bf.append(lost_size);
}
Activator.log(bf.toString(), x);
}
void onMemoryChanged(String id, boolean notify_references, boolean context_suspended, boolean mem_map) {
if (channel == null) return;
if (notify_references) {
String prs_id = id;
Object ctx_obj = context_map.get(id);
if (ctx_obj instanceof IRunControl.RunControlContext) {
String ctx_prs_id = ((IRunControl.RunControlContext)ctx_obj).getProcessID();
if (ctx_prs_id != null) prs_id = ctx_prs_id;
}
for (Object obj : context_map.values()) {
if (obj instanceof IRunControl.RunControlContext) {
IRunControl.RunControlContext subctx = (IRunControl.RunControlContext)obj;
if (prs_id.equals(subctx.getProcessID()) && !id.equals(subctx.getID())) {
TCFNode subnode = getNode(subctx.getID());
if (subnode instanceof TCFNodeExecContext) {
TCFNodeExecContext exe = (TCFNodeExecContext)subnode;
if (context_suspended) exe.onOtherContextSuspended();
else if (mem_map) exe.onMemoryMapChanged();
else exe.onMemoryChanged(null, null);
}
}
}
}
}
if (mem_blocks_update == null) new MemoryBlocksUpdate();
mem_blocks_update.add(id, context_suspended);
}
public TCFAction getActiveAction(String id) {
return active_actions.get(id);
}
String getContextActionResult(String id) {
return action_results.get(id);
}
public long getMinViewUpdatesInterval() {
return min_view_updates_interval;
}
public boolean getViewUpdatesThrottleEnabled() {
return view_updates_throttle_enabled;
}
public boolean getWaitForViewsUpdateAfterStep() {
return wait_for_views_update_after_step;
}
public boolean getDelayStackUpdateUtilLastStep() {
return delay_stack_update_until_last_step;
}
public boolean getChannelThrottleEnabled() {
return channel_throttle_enabled;
}
public boolean getStackFramesLimitEnabled() {
return stack_frames_limit_enabled;
}
public int getStackFramesLimitValue() {
return stack_frames_limit_value;
}
public boolean getShowFunctionArgNames() {
return show_function_arg_names;
}
public boolean getShowFunctionArgValues() {
return show_function_arg_values;
}
public boolean getAutoChildrenListUpdates() {
return auto_children_list_updates;
}
public boolean getDelayChildrenListUpdates() {
return delay_children_list_updates;
}
public boolean getShowFullErrorReports() {
return show_full_error_reports;
}
public boolean getHoverWhileRunning() {
return hover_while_running;
}
void onProxyInstalled(TCFModelProxy mp) {
IPresentationContext pc = mp.getPresentationContext();
model_proxies.add(mp);
if (launch_node != null && pc.getId().equals(IDebugUIConstants.ID_DEBUG_VIEW)) {
Protocol.invokeLater(new InitialSelection(mp));
}
}
void onProxyDisposed(TCFModelProxy mp) {
model_proxies.remove(mp);
}
private void onContextRemoved(String[] context_ids) {
HashSet<String> set = new HashSet<String>();
for (String id : context_ids) {
launch_node.onContextRemoved(id);
TCFNode node = getNode(id);
if (node instanceof TCFNodeExecContext) {
((TCFNodeExecContext)node).onContextRemoved();
for (TCFModelProxy p : model_proxies) {
p.saveExpandState(node);
p.clearAutoExpandStack(id);
}
}
action_results.remove(id);
if (mem_blocks_update != null) mem_blocks_update.changeset.remove(id);
set.add(id);
}
for (;;) {
int n = set.size();
for (Map.Entry<String,Object> e : context_map.entrySet()) {
Object obj = e.getValue();
if (obj instanceof IRunControl.RunControlContext) {
IRunControl.RunControlContext x = (IRunControl.RunControlContext)obj;
if (set.contains(x.getParentID())) set.add(x.getID());
String pid = x.getProcessID();
if (pid != null && set.contains(pid)) set.add(x.getID());
}
else if (obj instanceof IStackTrace.StackTraceContext) {
IStackTrace.StackTraceContext x = (IStackTrace.StackTraceContext)obj;
if (set.contains(x.getParentID())) set.add(x.getID());
}
else if (obj instanceof IRegisters.RegistersContext) {
IRegisters.RegistersContext x = (IRegisters.RegistersContext)obj;
if (set.contains(x.getParentID())) set.add(x.getID());
String pid = x.getProcessID();
if (pid != null && set.contains(pid)) set.add(x.getID());
}
else if (obj instanceof Throwable) {
set.add(e.getKey());
}
}
if (n == set.size()) break;
}
context_map.keySet().removeAll(set);
launch_node.onAnyContextAddedOrRemoved();
// Close debug session if the last context is removed:
onContextOrProcessRemoved();
updateAnnotations(null);
}
void onContextRunning() {
updateAnnotations(null);
}
Map<String,Object> getContextMap() {
return context_map;
}
private void onContextOrProcessRemoved() {
final int generation = ++auto_disconnect_generation;
Protocol.invokeLater(1000, new Runnable() {
public void run() {
if (generation != auto_disconnect_generation) return;
if (launch_node == null) return;
if (launch_node.isDisposed()) return;
TCFChildren children = launch_node.getFilteredChildren();
if (!children.validate(this)) return;
if (children.size() > 0) return;
launch.onLastContextRemoved();
}
});
}
void launchChanged() {
if (launch_node != null) {
for (TCFModelProxy p : model_proxies) {
String id = p.getPresentationContext().getId();
if (IDebugUIConstants.ID_DEBUG_VIEW.equals(id)) {
p.addDelta(launch_node, IModelDelta.STATE | IModelDelta.CONTENT);
}
}
}
else {
refreshLaunchView();
}
}
public TCFModelProxy[] getModelProxies(IPresentationContext ctx) {
TCFModelProxy[] proxies = new TCFModelProxy[0];
for (TCFModelProxy proxy : model_proxies) {
if (ctx.equals(proxy.getPresentationContext())) {
TCFModelProxy[] old_proxies = proxies;
proxies = new TCFModelProxy[proxies.length + 1];
System.arraycopy(old_proxies, 0, proxies, 0, old_proxies.length);
proxies[proxies.length - 1] = proxy;
}
}
return proxies;
}
public Collection<TCFModelProxy> getModelProxies() {
return model_proxies;
}
void dispose() {
assert Protocol.isDispatchThread();
if (launch_node != null) onDisconnected();
if (view_request_listeners != null) {
for (ITCFPresentationProvider p : view_request_listeners) {
try {
p.onModelDisposed(this);
}
catch (Throwable x) {
Activator.log("Unhandled exception in a presentation provider", x);
}
}
}
prefs_store.removePropertyChangeListener(prefs_listener);
launch.removeActionsListener(actions_listener);
expr_manager.removeExpressionListener(expressions_listener);
for (TCFConsole c : process_consoles.values()) c.close();
for (TCFConsole c : debug_consoles) c.close();
if (dprintf_console != null) dprintf_console.close();
process_consoles.clear();
debug_consoles.clear();
dprintf_console = null;
context_map.clear();
assert id2node.size() == 0;
disposed = true;
}
void addNode(String id, TCFNode node) {
assert id != null;
assert Protocol.isDispatchThread();
assert id2node.get(id) == null;
assert launch_node != null;
assert !node.isDisposed();
id2node.put(id, node);
}
void removeNode(String id) {
assert id != null;
assert Protocol.isDispatchThread();
for (TCFMemoryBlock b : mem_blocks) b.onContextExited(id);
id2node.remove(id);
}
void flushAllCaches() {
ISourceLocator l = launch.getSourceLocator();
if (l instanceof ISourceLookupDirector) {
ISourceLookupDirector d = (ISourceLookupDirector)l;
ISourceLookupParticipant[] participants = d.getParticipants();
for (int i = 0; i < participants.length; i++) {
ISourceLookupParticipant participant = participants[i];
participant.sourceContainersChanged(d);
}
}
for (TCFMemoryBlock b : mem_blocks) b.flushAllCaches();
for (TCFNode n : id2node.values()) n.flushAllCaches();
launch_node.flushAllCaches();
}
public IExpressionManager getExpressionManager() {
return expr_manager;
}
public Display getDisplay() {
return display;
}
/**
* @return debug model launch object.
*/
public TCFLaunch getLaunch() {
return launch;
}
/**
* @return communication channel that this model is using.
*/
public IChannel getChannel() {
return channel;
}
/**
* Get top level (root) debug model node.
* Same as getNode("").
* @return root node.
*/
public TCFNodeLaunch getRootNode() {
return launch_node;
}
/**
* Set current hover expression for a given model node,
* and return a cache of expression nodes that represents given expression.
* The model allows only one current hover expression per node at any time,
* however it will cache results of recent expression evaluations,
* and it will re-use cached results when current hover expression changes.
* The cache getData() method should not return more then 1 node,
* and it can return an empty collection.
* @param parent - a thread or stack frame where the expression should be evaluated.
* @param expression - the expression text, can be null.
* @return a cache of expression nodes.
*/
public TCFChildren getHoverExpressionCache(TCFNode parent, String expression) {
assert Protocol.isDispatchThread();
if (parent instanceof TCFNodeStackFrame) {
return ((TCFNodeStackFrame)parent).getHoverExpressionCache(expression);
}
if (parent instanceof TCFNodeExecContext) {
return ((TCFNodeExecContext)parent).getHoverExpressionCache(expression);
}
return null;
}
/**
* Get a model node with given ID.
* ID == "" means launch node.
* @param id - node ID.
* @return debug model node or null if no node exists with such ID.
*/
public TCFNode getNode(String id) {
if (id == null) return null;
if (id.equals("")) return launch_node;
assert Protocol.isDispatchThread();
return id2node.get(id);
}
/**
* Get a type that should be used to cast a value of an expression when it is shown in a view.
* Return null if original type of the value should be used.
* @param id - expression node ID.
* @return a string that designates a type or null.
*/
public String getCastToType(String id) {
return cast_to_type_map.get(id);
}
/**
* Register a type that should be used to cast a value of an expression when it is shown in a view.
* 'type' == null means original type of the value should be used.
* @param id - expression node ID.
* @param type - a string that designates a type.
*/
public void setCastToType(String id, String type) {
if (type != null && type.trim().length() == 0) type = null;
if (type == null) cast_to_type_map.remove(id);
else cast_to_type_map.put(id, type);
TCFNode node = id2node.get(id);
if (node instanceof ICastToType) {
((ICastToType)node).onCastToTypeChanged();
}
}
/**
* Get a data cache that contains properties of a symbol.
* New cache object is created if it does not exist yet.
* @param sym_id - the symbol ID.
* @return data cache object.
*/
public TCFDataCache<ISymbols.Symbol> getSymbolInfoCache(final String sym_id) {
if (sym_id == null) return null;
TCFNodeSymbol n = (TCFNodeSymbol)getNode(sym_id);
if (n == null) n = new TCFNodeSymbol(launch_node, sym_id);
return n.getContext();
}
/**
* Get a data cache that contains children of a symbol.
* New cache object is created if it does not exist yet.
* @param sym_id - the symbol ID.
* @return data cache object.
*/
public TCFDataCache<String[]> getSymbolChildrenCache(final String sym_id) {
if (sym_id == null) return null;
TCFNodeSymbol n = (TCFNodeSymbol)getNode(sym_id);
if (n == null) n = new TCFNodeSymbol(launch_node, sym_id);
return n.getChildren();
}
/**
* Get a data cache that contains location info of a symbol.
* New cache object is created if it does not exist yet.
* @param sym_id - the symbol ID.
* @return data cache object.
*/
public TCFDataCache<Map<String,Object>> getSymbolLocationCache(final String sym_id) {
if (sym_id == null) return null;
TCFNodeSymbol n = (TCFNodeSymbol)getNode(sym_id);
if (n == null) n = new TCFNodeSymbol(launch_node, sym_id);
return n.getLocation();
}
/**
* Search memory context that owns the object represented by given node.
* @return data cache item that holds the memory context node.
*/
public TCFDataCache<TCFNodeExecContext> searchMemoryContext(final TCFNode node) {
TCFNode n = node;
while (n != null && !n.isDisposed()) {
if (n instanceof TCFNodeExecContext) return ((TCFNodeExecContext)n).getMemoryNode();
n = n.parent;
}
return null;
}
/**
* Asynchronously create model node for given ID.
* If 'done' is TCFDataCache and it is valid after the method returns,
* the node cannot be created because of an error,
* and the cache will contain the error report.
* @param id - context ID.
* @param done - an object waiting for cache validation.
* @return - true if all done, false if 'done' is waiting for remote data.
*/
public boolean createNode(String id, final Runnable done) {
TCFNode parent = getNode(id);
if (parent != null) return true;
LinkedList<Object> path = null;
for (;;) {
Object obj = context_map.get(id);
if (obj == null) obj = new CreateNodeRunnable(id);
if (obj instanceof CreateNodeRunnable) {
((CreateNodeRunnable)obj).wait(done);
return false;
}
if (obj instanceof Throwable) {
if (done instanceof TCFDataCache<?>) {
((TCFDataCache<?>)done).set(null, (Throwable)obj, null);
}
return true;
}
if (path == null) path = new LinkedList<Object>();
path.add(obj);
String parent_id = null;
if (obj instanceof IRunControl.RunControlContext) {
parent_id = ((IRunControl.RunControlContext)obj).getParentID();
}
else if (obj instanceof IStackTrace.StackTraceContext) {
parent_id = ((IStackTrace.StackTraceContext)obj).getParentID();
}
else {
parent_id = ((IRegisters.RegistersContext)obj).getParentID();
}
parent = parent_id == null ? launch_node : getNode(parent_id);
if (parent != null) break;
id = parent_id;
}
while (path.size() > 0) {
Object obj = path.removeLast();
if (obj instanceof IRunControl.RunControlContext) {
IRunControl.RunControlContext ctx = (IRunControl.RunControlContext)obj;
TCFNodeExecContext n = new TCFNodeExecContext(parent, ctx.getID());
if (parent instanceof TCFNodeLaunch) ((TCFNodeLaunch)parent).getChildren().add(n);
else ((TCFNodeExecContext)parent).getChildren().add(n);
n.setRunContext(ctx);
parent = n;
}
else if (obj instanceof IStackTrace.StackTraceContext) {
IStackTrace.StackTraceContext ctx = (IStackTrace.StackTraceContext)obj;
TCFNodeStackFrame n = new TCFNodeStackFrame((TCFNodeExecContext)parent, ctx.getID(), false);
((TCFNodeExecContext)parent).getStackTrace().add(n);
parent = n;
}
else if (obj instanceof IRegisters.RegistersContext) {
IRegisters.RegistersContext ctx = (IRegisters.RegistersContext)obj;
TCFNodeRegister n = new TCFNodeRegister(parent, ctx.getID());
if (parent instanceof TCFNodeRegister) ((TCFNodeRegister)parent).getChildren().add(n);
else if (parent instanceof TCFNodeStackFrame) ((TCFNodeStackFrame)parent).getRegisters().add(n);
else ((TCFNodeExecContext)parent).getRegisters().add(n);
parent = n;
}
else {
assert false;
}
}
return true;
}
private class CreateNodeRunnable implements Runnable {
final String id;
final ArrayList<Runnable> waiting_list = new ArrayList<Runnable>();
final ArrayList<IService> service_list = new ArrayList<IService>();
CreateNodeRunnable(String id) {
this.id = id;
assert context_map.get(id) == null;
String[] arr = { IRunControl.NAME, IStackTrace.NAME, IRegisters.NAME };
for (String nm : arr) {
IService s = channel.getRemoteService(nm);
if (s != null) service_list.add(s);
}
context_map.put(id, this);
Protocol.invokeLater(this);
}
void wait(Runnable r) {
assert context_map.get(id) == this;
if (waiting_list.contains(r)) return;
waiting_list.add(r);
}
void done(Object res) {
assert res != null;
assert res != this;
context_map.put(id, res);
for (Runnable r : waiting_list) {
if (createNode(id, r)) {
if (r instanceof TCFDataCache<?>) ((TCFDataCache<?>)r).post();
else r.run();
}
}
}
public void run() {
if (context_map.get(id) != this) {
Object res = context_map.get(id);
if (res == null) res = new Exception("Invalid context ID");
done(res);
}
else if (service_list.size() == 0) {
done(new Exception("Invalid context ID"));
}
else {
IService s = service_list.remove(0);
if (s instanceof IRunControl) {
((IRunControl)s).getContext(id, new IRunControl.DoneGetContext() {
public void doneGetContext(IToken token, Exception error, IRunControl.RunControlContext context) {
if (error == null && context != null) done(context);
else run();
}
});
}
else if (s instanceof IStackTrace) {
((IStackTrace)s).getContext(new String[]{ id }, new IStackTrace.DoneGetContext() {
public void doneGetContext(IToken token, Exception error, IStackTrace.StackTraceContext[] context) {
if (error == null && context != null && context.length == 1 && context[0] != null) done(context[0]);
else run();
}
});
}
else {
((IRegisters)s).getContext(id, new IRegisters.DoneGetContext() {
public void doneGetContext(IToken token, Exception error, IRegisters.RegistersContext context) {
if (error == null && context != null) done(context);
else run();
}
});
}
}
}
}
public void update(IChildrenCountUpdate[] updates) {
for (IChildrenCountUpdate update : updates) {
Object o = update.getElement();
if (o instanceof TCFLaunch) {
if (launch_node != null) {
launch_node.update(update);
}
else {
update.setChildCount(0);
update.done();
}
}
else {
((TCFNode)o).update(update);
}
}
}
public void update(IChildrenUpdate[] updates) {
for (IChildrenUpdate update : updates) {
Object o = update.getElement();
if (o instanceof TCFLaunch) {
if (launch_node != null) {
launch_node.update(update);
}
else {
update.done();
}
}
else {
((TCFNode)o).update(update);
}
}
}
public void update(IHasChildrenUpdate[] updates) {
for (IHasChildrenUpdate update : updates) {
Object o = update.getElement();
if (o instanceof TCFLaunch) {
if (launch_node != null) {
launch_node.update(update);
}
else {
update.setHasChilren(false);
update.done();
}
}
else {
((TCFNode)o).update(update);
}
}
}
public void update(ILabelUpdate[] updates) {
for (ILabelUpdate update : updates) {
Object o = update.getElement();
// Launch label is provided by TCFLaunchLabelProvider class.
assert !(o instanceof TCFLaunch);
((TCFNode)o).update(update);
}
}
public void update(final IViewerInputUpdate update) {
Protocol.invokeLater(new Runnable() {
public void run() {
TCFNode node = pins.get(update.getPresentationContext().getPart());
if (node != null) {
node.update(update);
}
else {
if (IDebugUIConstants.ID_BREAKPOINT_VIEW.equals(update.getPresentationContext().getId())) {
// Current implementation does not support flexible hierarchy for breakpoints
IViewerInputProvider p = (IViewerInputProvider)launch.getAdapter(IViewerInputProvider.class);
if (p != null) {
p.update(update);
return;
}
}
Object o = update.getElement();
if (o instanceof TCFLaunch) {
update.setInputElement(o);
update.done();
}
else {
((TCFNode)o).update(update);
}
}
}
});
}
public void encodeElements(final IElementMementoRequest[] requests) {
for (IElementMementoRequest request : requests) {
Object element = request.getElement();
if (element instanceof TCFNode) ((TCFNode)element).encodeElement(request);
}
asyncExec(new Runnable() {
public void run() {
for (IElementMementoRequest request : requests) request.done();
}
});
}
public void compareElements(final IElementCompareRequest[] requests) {
for (IElementCompareRequest request : requests) {
Object element = request.getElement();
if (element instanceof TCFNode) ((TCFNode)element).compareElements(request);
}
asyncExec(new Runnable() {
public void run() {
for (IElementMementoRequest request : requests) request.done();
}
});
}
public IModelProxy createModelProxy(Object element, IPresentationContext context) {
return new TCFModelProxy(this);
}
public IColumnPresentation createColumnPresentation(IPresentationContext context, Object element) {
String id = getColumnPresentationId(context, element);
if (id == null) return null;
if (id.equals(TCFColumnPresentationRegister.PRESENTATION_ID)) return new TCFColumnPresentationRegister();
if (id.equals(TCFColumnPresentationExpression.PRESENTATION_ID)) return new TCFColumnPresentationExpression();
if (id.equals(TCFColumnPresentationModules.PRESENTATION_ID)) return new TCFColumnPresentationModules();
return null;
}
public String getColumnPresentationId(IPresentationContext context, Object element) {
if (IDebugUIConstants.ID_REGISTER_VIEW.equals(context.getId())) {
return TCFColumnPresentationRegister.PRESENTATION_ID;
}
if (IDebugUIConstants.ID_VARIABLE_VIEW.equals(context.getId())) {
return TCFColumnPresentationExpression.PRESENTATION_ID;
}
if (IDebugUIConstants.ID_EXPRESSION_VIEW.equals(context.getId())) {
return TCFColumnPresentationExpression.PRESENTATION_ID;
}
if (ID_EXPRESSION_HOVER.equals(context.getId())) {
return TCFColumnPresentationExpression.PRESENTATION_ID;
}
if (IDebugUIConstants.ID_MODULE_VIEW.equals(context.getId())) {
return TCFColumnPresentationModules.PRESENTATION_ID;
}
return null;
}
public void setPin(IWorkbenchPart part, TCFNode node) {
assert Protocol.isDispatchThread();
if (node == null) pins.remove(part);
else pins.put(part, node);
}
private IPresentationContext getPresentationContext(IWorkbenchPart part) {
if (part instanceof IDebugView) {
Viewer viewer = ((IDebugView)part).getViewer();
if (viewer instanceof ITreeModelViewer) {
ITreeModelViewer t = ((ITreeModelViewer)viewer);
return t.getPresentationContext();
}
}
return null;
}
public void setLock(IWorkbenchPart part) {
if (launch_node == null) return;
IPresentationContext ctx = getPresentationContext(part);
if (ctx == null) return;
locks.put(part, new TCFSnapshot(ctx));
for (TCFModelProxy proxy : model_proxies) {
if (ctx.equals(proxy.getPresentationContext())) {
proxy.addDelta((TCFNode)proxy.getInput(), IModelDelta.CONTENT);
}
}
}
public boolean isLocked(IWorkbenchPart part) {
return locks.get(part) != null;
}
public boolean clearLock(IWorkbenchPart part) {
TCFSnapshot snapshot = locks.remove(part);
if (snapshot == null) return false;
snapshot.dispose();
IPresentationContext ctx = getPresentationContext(part);
if (ctx != null) {
for (TCFModelProxy proxy : model_proxies) {
if (ctx.equals(proxy.getPresentationContext())) {
proxy.addDelta((TCFNode)proxy.getInput(), IModelDelta.CONTENT);
}
}
}
return true;
}
/**
* Set locking policy (aka update policy) for given workbench part.
* @param part - the workbench part.
* @param policy - locking policy code.
*/
public void setLockPolicy(IWorkbenchPart part, int policy) {
if (policy == UPDATE_POLICY_AUTOMATIC) {
clearLock(part);
lock_policy.remove(part);
}
else {
if (!isLocked(part)) setLock(part);
lock_policy.put(part, policy);
}
}
/**
* Return locking policy (aka update policy) for given workbench part.
* @param part - the workbench part.
* @return - locking policy code.
*/
public int getLockPolicy(IWorkbenchPart part) {
if (locks.get(part) == null) return UPDATE_POLICY_AUTOMATIC;
Integer i = lock_policy.get(part);
if (i == null || i.intValue() == 0) return UPDATE_POLICY_MANUAL;
return i.intValue();
}
/**
* Get a snapshot that is associated with given presentation context.
* @param ctx - the presentation context.
* @return - debug model snapshot, or null if the context is not locked.
*/
TCFSnapshot getSnapshot(IPresentationContext ctx) {
return locks.get(ctx.getPart());
}
/**
* Handle debug view selection and suspend triggers when debug context is suspended.
* @param node - model node that represents the debug context.
* @param reason - suspend reason.
*/
public void setDebugViewSelection(TCFNode node, String reason) {
assert Protocol.isDispatchThread();
if (node == null) return;
if (node.isDisposed()) return;
if (!SELECT_ADDED.equals(reason)) runSuspendTrigger(node);
if (initial_selection != null) Protocol.invokeLater(initial_selection);
if (reason == null) return;
for (TCFModelProxy proxy : model_proxies) {
if (proxy.getPresentationContext().getId().equals(IDebugUIConstants.ID_DEBUG_VIEW)) {
setDebugViewSelectionForProxy(proxy, node, reason);
}
if (reason.equals(IRunControl.REASON_BREAKPOINT)) {
IWorkbenchPart part = proxy.getPresentationContext().getPart();
int policy = getLockPolicy(part);
if (policy == UPDATE_POLICY_BREAKPOINT) {
clearLock(part);
setLock(part);
}
}
}
}
/**
* Sets the selection in debug view for a given debug view instance.
* @param proxy - model proxy for a given debug view instance
* @param node - model node that represents the debug context.
* @param reason - suspend reason.
* @return whether the node was selected
*/
public boolean setDebugViewSelectionForProxy(TCFModelProxy proxy, TCFNode node, String reason) {
assert Protocol.isDispatchThread();
if (!proxy.getPresentationContext().getId().equals(IDebugUIConstants.ID_DEBUG_VIEW)) return false;
if (node == null) return false;
if (node.isDisposed()) return false;
if (reason == null) return false;
boolean user_request =
reason.equals(IRunControl.REASON_USER_REQUEST) ||
reason.equals(IRunControl.REASON_STEP) ||
reason.equals(IRunControl.REASON_CONTAINER) ||
delay_stack_update_until_last_step && launch.getContextActionsCount(node.id) != 0;
if (proxy.getAutoExpandNode(node, user_request)) proxy.expand(node);
if (reason.equals(IRunControl.REASON_USER_REQUEST) || reason.equals(TCFModel.SELECT_ADDED)) return false;
if (initial_selection != null && !reason.equals(SELECT_INITIAL)) initial_selection = null;
proxy.setSelection(node);
return true;
}
/**
* Update debugger annotations in a workbench window.
* @param window - workbench window, null means update all windows.
*/
public void updateAnnotations(IWorkbenchWindow window) {
annotation_manager.updateAnnotations(window, launch);
}
private synchronized Object displaySourceStart(IWorkbenchPage page, boolean wait) {
Object generation = new Object();
if (wait) launch.addPendingClient(generation);
if (page != null) {
Object prev = display_source_generation.put(page, generation);
if (prev != null) launch.removePendingClient(prev);
}
return generation;
}
private synchronized boolean displaySourceCheck(IWorkbenchPage page, Object generation) {
return page == null || generation == display_source_generation.get(page);
}
private synchronized void displaySourceEnd(Object generation) {
launch.removePendingClient(generation);
}
/**
* Reveal source code associated with given model element.
* The method is part of ISourceDisplay interface.
* The method is normally called from SourceLookupService.
*/
public void displaySource(Object model_element, final IWorkbenchPage page, boolean forceSourceLookup) {
if (page != null) {
/*
* Because of racing in Eclipse Debug infrastructure, 'model_element' value can be invalid.
* As a workaround, get current debug view selection.
*/
ISelection context = DebugUITools.getDebugContextManager().getContextService(page.getWorkbenchWindow()).getActiveContext();
if (context instanceof IStructuredSelection) {
IStructuredSelection selection = (IStructuredSelection)context;
model_element = selection.isEmpty() ? null : selection.getFirstElement();
}
displaySource(model_element, page, DISPLAY_SOURCE_ON_SUSPEND);
}
}
private void displaySource(final Object element, final IWorkbenchPage page, final int event) {
final Object generation = displaySourceStart(page, wait_for_pc_update_after_step);
Protocol.invokeLater(25, new Runnable() {
public void run() {
if (!displaySourceCheck(page, generation)) return;
TCFNodeStackFrame stack_frame = null;
if (!disposed && channel.getState() == IChannel.STATE_OPEN) {
if (element instanceof TCFNodeExecContext) {
TCFNodeExecContext exec_ctx = (TCFNodeExecContext)element;
if (!exec_ctx.isDisposed() && active_actions.get(exec_ctx.id) == null) {
TCFDataCache<TCFContextState> state_cache = exec_ctx.getState();
if (!state_cache.validate(this)) return;
if (!exec_ctx.isNotActive()) {
TCFContextState state_data = state_cache.getData();
if (state_data != null && state_data.is_suspended) {
TCFChildrenStackTrace stack_trace = exec_ctx.getStackTrace();
if (!stack_trace.validate(this)) return;
stack_frame = stack_trace.getTopFrame();
}
}
}
}
else if (element instanceof TCFNodeStackFrame) {
TCFNodeStackFrame f = (TCFNodeStackFrame)element;
TCFNodeExecContext exec_ctx = (TCFNodeExecContext)f.parent;
if (!f.isDisposed() && !exec_ctx.isDisposed() && active_actions.get(exec_ctx.id) == null) {
TCFDataCache<TCFContextState> state_cache = exec_ctx.getState();
if (!state_cache.validate(this)) return;
if (!exec_ctx.isNotActive()) {
TCFContextState state_data = state_cache.getData();
if (state_data != null && state_data.is_suspended) {
// Validate stack trace to make sure stack_frame.getFrameNo() is valid
TCFChildrenStackTrace stack_trace = exec_ctx.getStackTrace();
if (!stack_trace.validate(this)) return;
stack_frame = f;
}
}
}
}
}
String ctx_id = null;
String mem_id = null;
boolean top_frame = false;
ILineNumbers.CodeArea area = null;
if (stack_frame != null) {
TCFDataCache<TCFSourceRef> line_info = stack_frame.getLineInfo();
if (!line_info.validate(this)) return;
Throwable error = line_info.getError();
TCFSourceRef src_ref = line_info.getData();
if (error == null && src_ref != null) error = src_ref.error;
if (error != null) Activator.log("Error retrieving source mapping for a stack frame", error);
if (src_ref != null) {
mem_id = src_ref.context_id;
area = src_ref.area;
}
top_frame = stack_frame.getFrameNo() == 0;
ctx_id = stack_frame.parent.id;
}
displaySource(generation, page, element, ctx_id, mem_id, top_frame, area, event);
}
});
}
/**
* Open source text editor.
* The editor is shown in currently active Eclipse window.
* Source file name is translated using source lookup settings of the debugger.
* "Source not found" window is shown if the file cannot be located.
* This method should be called on the display thread.
* @param context_id - debug context ID.
* @param source_file_name - compile-time source file name.
* @param line - scroll the editor to reveal this line.
* @return - text editor interface.
*/
public ITextEditor displaySource(final String context_id, String source_file_name, int line) {
if (PlatformUI.getWorkbench().isClosing()) return null;
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (window == null) return null;
String editor_id = null;
IEditorInput editor_input = null;
Object source_element = TCFSourceLookupDirector.lookup(launch, context_id, source_file_name);
if (source_element != null) {
ISourcePresentation presentation = TCFModelPresentation.getDefault();
editor_input = presentation.getEditorInput(source_element);
if (editor_input != null) editor_id = presentation.getEditorId(editor_input, source_element);
}
if (editor_input == null || editor_id == null) {
ILaunchConfiguration cfg = launch.getLaunchConfiguration();
TCFNode node = new TCFTask<TCFNode>(channel) {
public void run() {
if (!createNode(context_id, this)) return;
done(getNode(context_id));
}
}.getE();
if (node != null) {
ISourceNotFoundPresentation presentation = (ISourceNotFoundPresentation)DebugPlugin.getAdapter(node, ISourceNotFoundPresentation.class);
if (presentation != null) {
editor_input = presentation.getEditorInput(node, cfg, source_file_name);
editor_id = presentation.getEditorId(editor_input, node);
}
}
if (editor_id == null || editor_input == null) {
editor_id = IDebugUIConstants.ID_COMMON_SOURCE_NOT_FOUND_EDITOR;
editor_input = editor_not_found.get(cfg);
if (editor_input == null) {
editor_input = new CommonSourceNotFoundEditorInput(cfg);
editor_not_found.put(cfg, editor_input);
}
}
}
IWorkbenchPage page = window.getActivePage();
return displaySource(page, editor_id, editor_input, line);
}
private void displaySource(final Object generation, final IWorkbenchPage page,
final Object element, final String exe_id, final String mem_id, final boolean top_frame,
final ILineNumbers.CodeArea area, final int event) {
final boolean disassembly_available = channel.getRemoteService(IDisassembly.class) != null;
asyncExec(new Runnable() {
public void run() {
try {
if (!displaySourceCheck(page, generation)) return;
String editor_id = null;
IEditorInput editor_input = null;
int line = 0;
if (area != null) {
Object source_element = TCFSourceLookupDirector.lookup(launch, mem_id, area);
if (source_element != null) {
ISourcePresentation presentation = TCFModelPresentation.getDefault();
editor_input = presentation.getEditorInput(source_element);
if (editor_input != null) editor_id = presentation.getEditorId(editor_input, source_element);
line = area.start_line;
}
}
if (area != null && !instruction_stepping_enabled && (editor_input == null || editor_id == null)) {
ILaunchConfiguration cfg = launch.getLaunchConfiguration();
ISourceNotFoundPresentation presentation = (ISourceNotFoundPresentation)DebugPlugin.getAdapter(element, ISourceNotFoundPresentation.class);
if (presentation != null) {
String filename = TCFSourceLookupParticipant.toFileName(area);
editor_input = presentation.getEditorInput(element, cfg, filename);
editor_id = presentation.getEditorId(editor_input, element);
}
if (editor_id == null || editor_input == null) {
editor_id = IDebugUIConstants.ID_COMMON_SOURCE_NOT_FOUND_EDITOR;
editor_input = editor_not_found.get(cfg);
if (editor_input == null) {
editor_input = new CommonSourceNotFoundEditorInput(cfg);
editor_not_found.put(cfg, editor_input);
}
}
}
if (exe_id != null && disassembly_available &&
PlatformUI.getWorkbench().getEditorRegistry().findEditor(DisassemblyEditorInput.EDITOR_ID) != null) {
switch (event) {
case DISPLAY_SOURCE_ON_SUSPEND:
case DISPLAY_SOURCE_ON_REFRESH:
if (instruction_stepping_enabled || editor_input == null || editor_id == null) {
editor_id = DisassemblyEditorInput.EDITOR_ID;
editor_input = DisassemblyEditorInput.INSTANCE;
}
break;
case DISPLAY_SOURCE_ON_STEP_MODE:
if (instruction_stepping_enabled) {
editor_id = DisassemblyEditorInput.EDITOR_ID;
editor_input = DisassemblyEditorInput.INSTANCE;
break;
}
break;
}
}
if (!displaySourceCheck(page, generation)) return;
displaySource(page, editor_id, editor_input, line);
if (wait_for_pc_update_after_step) launch.addPendingClient(annotation_manager);
updateAnnotations(page.getWorkbenchWindow());
}
finally {
displaySourceEnd(generation);
}
}
});
}
/*
* Open an editor for given editor input and scroll it to reveal given line.
* @param page - workbench page that will contain the editor
* @param editor_id - editor type ID
* @param editor_input - IEditorInput representing a source file to be shown in the editor
* @param line - scroll the editor to reveal this line.
* @return - IEditorPart if the editor was opened successfully, or null otherwise.
*/
private ITextEditor displaySource(IWorkbenchPage page, String editor_id, IEditorInput editor_input, int line) {
ITextEditor text_editor = null;
if (page != null && editor_input != null && editor_id != null) {
IEditorPart editor = openEditor(editor_input, editor_id, page);
if (editor instanceof ITextEditor) {
text_editor = (ITextEditor)editor;
}
else if (editor != null) {
text_editor = (ITextEditor)editor.getAdapter(ITextEditor.class);
}
}
IRegion region = null;
if (text_editor != null) {
region = getLineInformation(text_editor, line);
if (region != null) text_editor.selectAndReveal(region.getOffset(), 0);
}
return text_editor;
}
/*
* Refresh source view when memory or path mappings change.
*/
private void refreshSourceView() {
asyncExec(new Runnable() {
public void run() {
if (!PlatformUI.isWorkbenchRunning()) return;
IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows();
if (windows == null) return;
for (IWorkbenchWindow window : windows) {
IWorkbenchPage page = window.getActivePage();
if (page != null) {
ISelection context = DebugUITools.getDebugContextManager().getContextService(page.getWorkbenchWindow()).getActiveContext();
if (context instanceof IStructuredSelection) {
IStructuredSelection selection = (IStructuredSelection)context;
Object element = selection.isEmpty() ? null : selection.getFirstElement();
if (element != null) displaySource(element, page, DISPLAY_SOURCE_ON_REFRESH);
}
}
}
}
});
}
/*
* Refresh Launch View.
* Normally the view is updated by sending deltas through model proxy.
* This method is used only when launch is not yet connected or already disconnected.
*/
private void refreshLaunchView() {
// TODO: there should be a better way to refresh Launch View
asyncExec(new Runnable() {
public void run() {
if (!PlatformUI.isWorkbenchRunning()) return;
IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows();
if (windows == null) return;
for (IWorkbenchWindow window : windows) {
IWorkbenchPage page = window.getActivePage();
if (page != null) {
IDebugView view = (IDebugView)page.findView(IDebugUIConstants.ID_DEBUG_VIEW);
if (view != null) ((StructuredViewer)view.getViewer()).refresh(launch);
}
}
}
});
}
/**
* Open debugger console that provide command line UI for the debugger.
*/
public void showDebugConsole() {
debug_consoles.add(new TCFConsole(this, TCFConsole.TYPE_CMD_LINE, null));
}
/**
* Show error message box in active workbench window.
* @param title - message box title.
* @param error - error to be shown.
*/
public void showMessageBox(final String title, final Throwable error) {
asyncExec(new Runnable() {
public void run() {
Shell shell = display.getActiveShell();
if (shell == null) {
Shell[] shells = display.getShells();
HashSet<Shell> set = new HashSet<Shell>();
for (Shell s : shells) set.add(s);
for (Shell s : shells) {
if (s.getParent() != null) set.remove(s.getParent().getShell());
}
for (Shell s : shells) shell = s;
}
MessageBox mb = new MessageBox(shell, SWT.ICON_ERROR | SWT.OK);
mb.setText(title);
mb.setMessage(getErrorMessage(error, true));
mb.open();
}
});
}
/**
* Create human readable error message from a Throwable object.
* @param error - a Throwable object.
* @param multiline - true if multi-line text is allowed.
* @return error message text.
*/
public static String getErrorMessage(Throwable error, boolean multiline) {
StringBuffer buf = new StringBuffer();
while (error != null) {
String msg = null;
if (!multiline && error instanceof IErrorReport) {
msg = Command.toErrorString(((IErrorReport)error).getAttributes());
}
else if (error instanceof UnknownHostException) {
msg = "Unknown host: " + error.getMessage();
}
else {
msg = error.getLocalizedMessage();
}
if (msg == null || msg.length() == 0) msg = error.getClass().getName();
buf.append(msg);
error = error.getCause();
if (error != null) {
char ch = buf.charAt(buf.length() - 1);
if (multiline && ch != '\n') {
buf.append('\n');
}
else if (ch != '.' && ch != ';') {
buf.append(';');
}
if (multiline) buf.append("Caused by:\n");
else buf.append(' ');
}
}
if (buf.length() > 0) {
char ch = buf.charAt(buf.length() - 1);
if (multiline && ch != '\n') {
buf.append('\n');
}
}
return buf.toString();
}
/*
* Open an editor for given editor input.
* @param input - IEditorInput representing a source file to be shown in the editor
* @param id - editor type ID
* @param page - workbench page that will contain the editor
* @return - IEditorPart if the editor was opened successfully, or null otherwise.
*/
private IEditorPart openEditor(final IEditorInput input, final String id, final IWorkbenchPage page) {
final IEditorPart[] editor = new IEditorPart[]{ null };
Runnable r = new Runnable() {
public void run() {
if (!page.getWorkbenchWindow().getWorkbench().isClosing()) {
try {
editor[0] = page.openEditor(input, id, false, IWorkbenchPage.MATCH_ID|IWorkbenchPage.MATCH_INPUT);
}
catch (PartInitException e) {
Activator.log("Cannot open editor", e);
}
}
}
};
BusyIndicator.showWhile(display, r);
return editor[0];
}
/*
* Returns the line information for the given line in the given editor
*/
private IRegion getLineInformation(ITextEditor editor, int line) {
IDocumentProvider provider = editor.getDocumentProvider();
IEditorInput input = editor.getEditorInput();
try {
provider.connect(input);
}
catch (CoreException e) {
return null;
}
try {
IDocument document = provider.getDocument(input);
if (document != null) return document.getLineInformation(line - 1);
}
catch (BadLocationException e) {
}
finally {
provider.disconnect(input);
}
return null;
}
/**
* Registers the given listener for suspend notifications.
* @param listener suspend listener
*/
public synchronized void addSuspendTriggerListener(ISuspendTriggerListener listener) {
suspend_trigger_listeners.add(listener);
}
/**
* Unregisters the given listener for suspend notifications.
* @param listener suspend listener
*/
public synchronized void removeSuspendTriggerListener(ISuspendTriggerListener listener) {
suspend_trigger_listeners.remove(listener);
}
/*
* Lazily run registered suspend listeners.
* @param node - suspended context.
*/
private synchronized void runSuspendTrigger(final TCFNode node) {
initial_suspend_trigger = null;
if (suspend_trigger_listeners.size() == 0) return;
final ISuspendTriggerListener[] listeners = suspend_trigger_listeners.toArray(
new ISuspendTriggerListener[suspend_trigger_listeners.size()]);
final int generation = ++suspend_trigger_generation;
if (wait_for_views_update_after_step) {
launch.addPendingClient(suspend_trigger_listeners);
}
asyncExec(new Runnable() {
public void run() {
synchronized (TCFModel.this) {
if (generation != suspend_trigger_generation) return;
}
for (final ISuspendTriggerListener listener : listeners) {
try {
listener.suspended(launch, node);
}
catch (Throwable x) {
Activator.log(x);
}
}
synchronized (TCFModel.this) {
if (generation != suspend_trigger_generation) return;
launch.removePendingClient(suspend_trigger_listeners);
}
}
});
}
/**
* Set whether instruction stepping mode should be enabled or not.
* @param enabled
*/
public void setInstructionSteppingEnabled(boolean enabled) {
instruction_stepping_enabled = enabled;
TCFNode node = (TCFNode)DebugUITools.getDebugContext().getAdapter(TCFNode.class);
if (node != null && node.model == this) {
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (window != null) {
IWorkbenchPage page = window.getActivePage();
if (page != null) displaySource(node, page, DISPLAY_SOURCE_ON_STEP_MODE);
}
}
}
/**
* @return whether instruction stepping is enabled
*/
public boolean isInstructionSteppingEnabled() {
return instruction_stepping_enabled;
}
/**
* Set whether reverse debugging should be enabled or not.
* @param enabled
*/
public void setReverseDebugEnabled(boolean enabled) {
reverse_debug_enabled = enabled;
}
/**
* @return whether reverse debugging is enabled.
*/
public boolean isReverseDebugEnabled() {
return reverse_debug_enabled;
}
/**
* @return whether to show qualified type names.
*/
public boolean isShowQualifiedTypeNamesEnabled() {
return qualified_type_names_enabled;
}
/**
* @return whether to filter variant fields by discriminant value
*/
public boolean isFilterVariantsByDiscriminant() {
return filter_variants_by_discriminant;
}
/*-------------------- Profiling/tracing interface -------------------------------- */
public interface ProfilerDataListener {
void onDataReceived(String ctx, Map<String,Object> data[]);
}
private final List<ProfilerDataListener> profiler_listeners =
new ArrayList<ProfilerDataListener>();
private final Map<String,TCFDataCache<Map<String,Object>>> profiler_capabilities =
new HashMap<String,TCFDataCache<Map<String,Object>>>();
private final Map<String,Map<String,Object>> profiler_configuration =
new HashMap<String,Map<String,Object>>();
private final Map<String,IToken> profiler_read_cmds = new HashMap<String,IToken>();
private final Runnable profiler_read_event = new Runnable() {
@Override
public void run() {
assert profiler_read_posted;
IProfiler profiler = channel.getRemoteService(IProfiler.class);
for (final String ctx : profiler_configuration.keySet()) {
if (profiler_read_cmds.get(ctx) != null) continue;
profiler_read_cmds.put(ctx, profiler.read(ctx, new IProfiler.DoneRead() {
@Override
public void doneRead(IToken token, Exception error, Map<String,Object>[] data) {
profiler_read_cmds.remove(ctx);
if (error != null && channel.getState() == IChannel.STATE_OPEN) {
Protocol.log("Cannot read profiler data", error);
}
if (data != null) {
for (ProfilerDataListener listener : profiler_listeners) {
listener.onDataReceived(ctx, data);
}
}
}
}));
}
if (profiler_configuration.size() > 0) {
Protocol.invokeLater(profiler_read_delay, this);
}
else {
profiler_read_posted = false;
}
}
};
private boolean profiler_read_posted;
private long profiler_read_delay = 4000;
/**
* Get profiler capabilities data for given context ID.
* See Profiler service documentation for more details.
* @param ctx - debug context ID.
* @return profiler capabilities.
*/
public TCFDataCache<Map<String,Object>> getProfilerCapabilities(final String ctx) {
assert Protocol.isDispatchThread();
TCFDataCache<Map<String,Object>> cache = profiler_capabilities.get(ctx);
if (cache == null) {
cache = new TCFDataCache<Map<String,Object>>(channel) {
@Override
protected boolean startDataRetrieval() {
IProfiler profiler = channel.getRemoteService(IProfiler.class);
if (profiler == null) {
set(null, null, new HashMap<String,Object>());
return false;
}
command = profiler.getCapabilities(ctx, new IProfiler.DoneGetCapabilities() {
@Override
public void doneGetCapabilities(IToken token, Exception error, Map<String,Object> capabilities) {
if (error instanceof IErrorReport) {
IErrorReport r = (IErrorReport)error;
if (r.getErrorCode() == IErrorReport.TCF_ERROR_INV_COMMAND) {
// Backward compatibility
set(token, null, null);
return;
}
}
set(token, error, capabilities);
}
});
return false;
}
};
profiler_capabilities.put(ctx, cache);
}
return cache;
}
/**
* Get profiler configuration data for given context ID.
* See Profiler service documentation for more details.
* @param ctx - debug context ID.
* @return profiler configuration.
*/
public Map<String,Object> getProfilerConfiguration(String ctx) {
assert Protocol.isDispatchThread();
Map<String,Object> m = profiler_configuration.get(ctx);
if (m == null) profiler_configuration.put(ctx, m = new HashMap<String,Object>());
return m;
}
/**
* Send profiler configuration to remote peer.
* Clients should call this method after making changes in the profiler configuration.
* @param ctx - debug context ID.
*/
public void sendProfilerConfiguration(String ctx) {
assert Protocol.isDispatchThread();
Map<String,Object> m = profiler_configuration.get(ctx);
IProfiler profiler = channel.getRemoteService(IProfiler.class);
if (profiler == null) return;
if (m.size() == 0) profiler_configuration.remove(ctx);
profiler.configure(ctx, m, new IProfiler.DoneConfigure() {
@Override
public void doneConfigure(IToken token, final Exception error) {
if (error != null) {
channel.terminate(error);
}
else if (!profiler_read_posted && profiler_configuration.size() > 0) {
Protocol.invokeLater(profiler_read_event);
profiler_read_posted = true;
}
}
});
}
/**
* Get delay between profiler data reads.
* @return delay in milliseconds.
*/
public long getProfilerReadDelay() {
return profiler_read_delay;
}
/**
* Set delay between profiler data reads.
* @param - delay in milliseconds.
*/
public void setProfilerReadDelay(long delay) {
if (delay < 100) delay = 100;
profiler_read_delay = delay;
}
/**
* Add profiler data listener.
* @param listener
*/
public void addProfilerDataListener(ProfilerDataListener listener) {
assert Protocol.isDispatchThread();
profiler_listeners.add(listener);
}
/**
* Remove profiler data listener.
* @param listener
*/
public void removeProfilerDataListener(ProfilerDataListener listener) {
assert Protocol.isDispatchThread();
profiler_listeners.remove(listener);
}
}