/*******************************************************************************
* Copyright (c) 2008, 2011 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.tm.internal.tcf.debug.ui.model;
import java.math.BigInteger;
import java.util.LinkedList;
import java.util.Map;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IExpressionManager;
import org.eclipse.debug.core.model.IExpression;
import org.eclipse.debug.core.model.IWatchExpression;
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.IElementEditor;
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.IPresentationContext;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.tm.internal.tcf.debug.model.TCFContextState;
import org.eclipse.tm.internal.tcf.debug.ui.Activator;
import org.eclipse.tm.internal.tcf.debug.ui.ImageCache;
import org.eclipse.tm.tcf.protocol.IChannel;
import org.eclipse.tm.tcf.protocol.IToken;
import org.eclipse.tm.tcf.protocol.JSON;
import org.eclipse.tm.tcf.protocol.Protocol;
import org.eclipse.tm.tcf.services.IExpressions;
import org.eclipse.tm.tcf.services.IMemory;
import org.eclipse.tm.tcf.services.IMemory.MemoryError;
import org.eclipse.tm.tcf.services.IRegisters;
import org.eclipse.tm.tcf.services.ISymbols;
import org.eclipse.tm.tcf.util.TCFDataCache;
import org.eclipse.tm.tcf.util.TCFTask;
public class TCFNodeExpression extends TCFNode implements IElementEditor, ICastToType, IWatchInExpressions, IDetailsProvider {
// TODO: User commands: Add Global Variables, Remove Global Variables
// TODO: enable Change Value user command
private final String script;
private final int index;
private final boolean deref;
private final String field_id;
private final String reg_id;
private final TCFData<IExpressions.Expression> var_expression;
private final TCFData<String> base_text;
private final TCFData<Expression> expression;
private final TCFData<IExpressions.Value> value;
private final TCFData<ISymbols.Symbol> type;
private final TCFData<String> type_name;
private final TCFData<String> string;
private final TCFData<String> expression_text;
private final TCFChildrenSubExpressions children;
private int sort_pos;
private boolean enabled = true;
private IExpressions.Value prev_value;
private IExpressions.Value next_value;
private static final RGB
rgb_error = new RGB(192, 0, 0),
rgb_highlight = new RGB(255, 255, 128),
rgb_disabled = new RGB(127, 127, 127);
private static int expr_cnt;
private class Expression {
final IExpressions.Expression expression;
boolean must_be_disposed;
Expression(IExpressions.Expression expression) {
assert expression != null;
this.expression = expression;
}
void dispose() {
if (!must_be_disposed) return;
if (channel.getState() == IChannel.STATE_OPEN) {
IExpressions exps = channel.getRemoteService(IExpressions.class);
exps.dispose(expression.getID(), new IExpressions.DoneDispose() {
public void doneDispose(IToken token, Exception error) {
if (error == null) return;
if (channel.getState() != IChannel.STATE_OPEN) return;
Activator.log("Error disposing remote expression evaluator", error);
}
});
}
must_be_disposed = false;
}
}
TCFNodeExpression(final TCFNode parent, final String script,
final String field_id, final String var_id, final String reg_id,
final int index, final boolean deref) {
super(parent, var_id != null ? var_id : "Expr" + expr_cnt++);
assert script != null || field_id != null || var_id != null || reg_id != null || index >= 0;
this.script = script;
this.field_id = field_id;
this.reg_id = reg_id;
this.index = index;
this.deref = deref;
var_expression = new TCFData<IExpressions.Expression>(channel) {
@Override
protected boolean startDataRetrieval() {
IExpressions exps = launch.getService(IExpressions.class);
if (exps == null || var_id == null) {
set(null, null, null);
return true;
}
command = exps.getContext(var_id, new IExpressions.DoneGetContext() {
public void doneGetContext(IToken token, Exception error, IExpressions.Expression context) {
set(token, error, context);
}
});
return false;
}
};
base_text = new TCFData<String>(channel) {
@Override
protected boolean startDataRetrieval() {
/* Compute expression script, not including type cast */
if (script != null) {
set(null, null, script);
return true;
}
if (var_id != null) {
if (!var_expression.validate(this)) return false;
Throwable err = null;
String exp = null;
if (var_expression.getData() == null) {
err = var_expression.getError();
}
else {
exp = var_expression.getData().getExpression();
if (exp == null) err = new Exception("Missing 'Expression' property");
}
set(null, err, exp);
return true;
}
if (reg_id != null) {
set(null, null, "${" + reg_id + "}");
return true;
}
TCFNode n = parent;
while (n instanceof TCFNodeArrayPartition) n = n.parent;
TCFDataCache<String> t = ((TCFNodeExpression)n).base_text;
if (!t.validate(this)) return false;
String e = t.getData();
if (e == null) {
set(null, t.getError(), null);
return true;
}
String cast = model.getCastToType(n.id);
if (cast != null) e = "(" + cast + ")(" + e + ")";
if (field_id != null) {
e = "(" + e + ")" + (deref ? "->" : ".") + "${" + field_id + "}";
}
else if (index == 0) {
e = "*(" + e + ")";
}
else if (index > 0) {
e = "(" + e + ")[" + index + "]";
}
set(null, null, e);
return true;
}
};
expression_text = new TCFData<String>(channel) {
@Override
protected boolean startDataRetrieval() {
/* Compute human readable expression script,
* including type cast, and using variable names instead of IDs */
String expr_text = null;
if (script != null) {
expr_text = script;
}
else if (var_id != null) {
if (!var_expression.validate(this)) return false;
IExpressions.Expression e = var_expression.getData();
if (e != null) {
TCFDataCache<ISymbols.Symbol> var = model.getSymbolInfoCache(e.getSymbolID());
if (var != null) {
if (!var.validate(this)) return false;
if (var.getData() != null) expr_text = var.getData().getName();
}
}
}
else if (reg_id != null) {
if (!model.createNode(reg_id, this)) return false;
if (isValid()) return true;
TCFNodeRegister reg_node = (TCFNodeRegister)model.getNode(reg_id);
for (;;) {
TCFDataCache<IRegisters.RegistersContext> ctx_cache = reg_node.getContext();
if (!ctx_cache.validate(this)) return false;
IRegisters.RegistersContext ctx_data = ctx_cache.getData();
if (ctx_data == null) {
set(null, ctx_cache.getError(), null);
return true;
}
expr_text = expr_text == null ? ctx_data.getName() : ctx_data.getName() + "." + expr_text;
if (!(reg_node.parent instanceof TCFNodeRegister)) break;
reg_node = (TCFNodeRegister)reg_node.parent;
}
expr_text = "$" + expr_text;
}
else {
TCFDataCache<?> pending = null;
TCFDataCache<ISymbols.Symbol> field = model.getSymbolInfoCache(field_id);
if (field != null && !field.validate()) pending = field;
if (!base_text.validate()) pending = base_text;
if (pending != null) {
pending.wait(this);
return false;
}
String parent_text = "";
TCFNode n = parent;
while (n instanceof TCFNodeArrayPartition) n = n.parent;
TCFDataCache<String> parent_text_cache = ((TCFNodeExpression)n).expression_text;
if (!parent_text_cache.validate(this)) return false;
if (parent_text_cache.getData() != null) {
parent_text = parent_text_cache.getData();
// surround with parentheses if not a simple identifier
if (!parent_text.matches("\\w*")) {
parent_text = '(' + parent_text + ')';
}
}
if (index >= 0) {
if (index == 0 && deref) {
expr_text = "*" + parent_text;
}
else {
expr_text = parent_text + "[" + index + "]";
}
}
if (expr_text == null && field != null && field.getData() != null) {
expr_text = parent_text + (deref ? "->" : ".") + field.getData().getName();
}
if (expr_text == null && base_text.getData() != null) expr_text = base_text.getData();
}
if (expr_text != null) {
String cast = model.getCastToType(id);
if (cast != null) expr_text = "(" + cast + ")(" + expr_text + ")";
}
set(null, null, expr_text);
return true;
}
};
expression = new TCFData<Expression>(channel) {
@Override
protected boolean startDataRetrieval() {
IExpressions exps = launch.getService(IExpressions.class);
if (exps == null) {
set(null, null, null);
return true;
}
String cast = model.getCastToType(id);
if (var_id != null && cast == null) {
if (!var_expression.validate(this)) return false;
Expression exp = null;
if (var_expression.getData() != null) exp = new Expression(var_expression.getData());
set(null, var_expression.getError(), exp);
return true;
}
if (!base_text.validate(this)) return false;
String e = base_text.getData();
if (e == null) {
set(null, base_text.getError(), null);
return true;
}
if (cast != null) e = "(" + cast + ")(" + e + ")";
TCFNode n = getRootExpression().parent;
if (n instanceof TCFNodeStackFrame && ((TCFNodeStackFrame)n).isEmulated()) n = n.parent;
command = exps.create(n.id, null, e, new IExpressions.DoneCreate() {
public void doneCreate(IToken token, Exception error, IExpressions.Expression context) {
Expression e = null;
if (context != null) {
e = new Expression(context);
e.must_be_disposed = true;
}
if (!isDisposed()) set(token, error, e);
else if (e != null) e.dispose();
}
});
return false;
}
@Override
public void cancel() {
if (isValid() && getData() != null) getData().dispose();
super.cancel();
}
@Override
public void dispose() {
if (isValid() && getData() != null) getData().dispose();
super.dispose();
}
};
value = new TCFData<IExpressions.Value>(channel) {
@Override
protected boolean startDataRetrieval() {
Boolean b = usePrevValue(this);
if (b == null) return false;
if (b) {
set(null, null, prev_value);
return true;
}
if (!expression.validate(this)) return false;
final Expression exp = expression.getData();
if (exp == null) {
set(null, expression.getError(), null);
return true;
}
IExpressions exps = launch.getService(IExpressions.class);
command = exps.evaluate(exp.expression.getID(), new IExpressions.DoneEvaluate() {
public void doneEvaluate(IToken token, Exception error, IExpressions.Value value) {
if (error != null) {
Boolean b = usePrevValue(null);
if (b != null && b) {
set(token, null, prev_value);
return;
}
}
set(token, error, value);
}
});
return false;
}
public void reset() {
super.reset();
}
};
type = new TCFData<ISymbols.Symbol>(channel) {
@Override
protected boolean startDataRetrieval() {
String type_id = null;
if (!value.validate(this)) return false;
IExpressions.Value val = value.getData();
if (val != null) type_id = val.getTypeID();
if (type_id == null) {
if (!expression.validate(this)) return false;
Expression exp = expression.getData();
if (exp != null) type_id = exp.expression.getTypeID();
}
if (type_id == null) {
set(null, value.getError(), null);
return true;
}
TCFDataCache<ISymbols.Symbol> type_cache = model.getSymbolInfoCache(type_id);
if (type_cache == null) {
set(null, null, null);
return true;
}
if (!type_cache.validate(this)) return false;
set(null, type_cache.getError(), type_cache.getData());
return true;
}
};
string = new TCFData<String>(channel) {
IMemory.MemoryContext mem;
ISymbols.Symbol base_type_data;
BigInteger addr;
byte[] buf;
int size;
int offs;
@Override
protected boolean startDataRetrieval() {
if (addr != null) {
if (mem == null) {
TCFDataCache<TCFNodeExecContext> mem_node_cache = model.searchMemoryContext(parent);
if (mem_node_cache == null) {
set(null, new Exception("Context does not provide memory access"), null);
return true;
}
if (!mem_node_cache.validate(this)) return false;
if (mem_node_cache.getError() != null) {
set(null, mem_node_cache.getError(), null);
return true;
}
TCFNodeExecContext mem_node = mem_node_cache.getData();
if (mem_node == null) {
set(null, new Exception("Context does not provide memory access"), null);
return true;
}
TCFDataCache<IMemory.MemoryContext> mem_ctx_cache = mem_node.getMemoryContext();
if (!mem_ctx_cache.validate(this)) return false;
if (mem_ctx_cache.getError() != null) {
set(null, mem_ctx_cache.getError(), null);
return true;
}
mem = mem_ctx_cache.getData();
if (mem == null) {
set(null, new Exception("Context does not provide memory access"), null);
return true;
}
}
if (size == 0) {
// data is ASCII string
if (buf == null) buf = new byte[256];
if (offs >= buf.length) {
byte[] tmp = new byte[buf.length * 2];
System.arraycopy(buf, 0, tmp, 0, buf.length);
buf = tmp;
}
command = mem.get(addr.add(BigInteger.valueOf(offs)), 1, buf, offs, 1, 0, new IMemory.DoneMemory() {
public void doneMemory(IToken token, MemoryError error) {
if (error != null) {
set(command, error, null);
}
else if (buf[offs] == 0 || offs >= 2048) {
set(command, null, toASCIIString(buf, 0, offs, '"'));
}
else if (command == token) {
command = null;
offs++;
run();
}
}
});
return false;
}
// data is a struct
if (offs != size) {
if (buf == null || buf.length < size) buf = new byte[size];
command = mem.get(addr, 1, buf, 0, size, 0, new IMemory.DoneMemory() {
public void doneMemory(IToken token, MemoryError error) {
if (error != null) {
set(command, error, null);
}
else if (command == token) {
command = null;
offs = size;
run();
}
}
});
return false;
}
StyledStringBuffer bf = new StyledStringBuffer();
bf.append('{');
if (!appendCompositeValueText(bf, 1, base_type_data, buf, 0, size, mem.isBigEndian(), this)) return false;
bf.append('}');
set(null, null, bf.toString());
return true;
}
if (!type.validate(this)) return false;
ISymbols.Symbol type_data = type.getData();
if (type_data != null) {
switch (type_data.getTypeClass()) {
case pointer:
case array:
TCFDataCache<ISymbols.Symbol> base_type_cahce = model.getSymbolInfoCache(type_data.getBaseTypeID());
if (base_type_cahce != null) {
if (!base_type_cahce.validate(this)) return false;
base_type_data = base_type_cahce.getData();
if (base_type_data != null) {
offs = 0;
size = base_type_data.getSize();
switch (base_type_data.getTypeClass()) {
case integer:
case cardinal:
if (base_type_data.getSize() != 1) break;
size = 0; // read until character = 0
case composite:
if (base_type_data.getSize() == 0) break;
if (type_data.getTypeClass() == ISymbols.TypeClass.array &&
base_type_data.getTypeClass() == ISymbols.TypeClass.composite) break;
if (!value.validate(this)) return false;
IExpressions.Value v = value.getData();
if (v != null) {
byte[] data = v.getValue();
if (type_data.getTypeClass() == ISymbols.TypeClass.array) {
set(null, null, toASCIIString(data, 0, data.length, '"'));
return true;
}
BigInteger a = TCFNumberFormat.toBigInteger(data, 0, data.length, v.isBigEndian(), false);
if (!a.equals(BigInteger.valueOf(0))) {
addr = a;
Protocol.invokeLater(this);
return false;
}
}
}
}
}
break;
case integer:
case cardinal:
if (type_data.getSize() == 1) {
if (!value.validate(this)) return false;
IExpressions.Value v = value.getData();
if (v != null) {
byte[] data = v.getValue();
set(null, null, toASCIIString(data, 0, data.length, '\''));
return true;
}
}
break;
}
}
set(null, null, null);
return true;
}
@Override
public void reset() {
super.reset();
mem = null;
addr = null;
}
};
type_name = new TCFData<String>(channel) {
@Override
protected boolean startDataRetrieval() {
if (!type.validate(this)) return false;
if (type.getData() == null) {
if (!value.validate(this)) return false;
IExpressions.Value val = value.getData();
if (val != null && val.getValue() != null) {
String s = getTypeName(val.getTypeClass(), val.getValue().length);
if (s != null) {
set(null, null, s);
return true;
}
}
}
StringBuffer bf = new StringBuffer();
if (!getTypeName(bf, type, this)) return false;
set(null, null, bf.toString());
return true;
}
};
children = new TCFChildrenSubExpressions(this, 0, 0, 0);
}
private TCFNodeExpression getRootExpression() {
TCFNode n = this;
while (n.parent instanceof TCFNodeExpression || n.parent instanceof TCFNodeArrayPartition) n = n.parent;
return (TCFNodeExpression)n;
}
private void postAllChangedDelta() {
TCFNodeExpression n = getRootExpression();
for (TCFModelProxy p : model.getModelProxies()) {
String id = p.getPresentationContext().getId();
if (IDebugUIConstants.ID_EXPRESSION_VIEW.equals(id) && n.script != null ||
TCFModel.ID_EXPRESSION_HOVER.equals(id) && n.script != null ||
IDebugUIConstants.ID_VARIABLE_VIEW.equals(id) && n.script == null) {
p.addDelta(this, IModelDelta.STATE | IModelDelta.CONTENT);
}
}
}
void onSuspended() {
prev_value = next_value;
if (expression.isValid() && expression.getError() != null) expression.reset();
value.reset();
type.reset();
type_name.reset();
string.reset();
children.onSuspended();
// No need to post delta: parent posted CONTENT
}
void onRegisterValueChanged() {
value.reset();
type.reset();
type_name.reset();
string.reset();
children.onRegisterValueChanged();
postAllChangedDelta();
}
void onMemoryChanged() {
value.reset();
type.reset();
type_name.reset();
string.reset();
children.onMemoryChanged();
if (parent instanceof TCFNodeExpression) return;
if (parent instanceof TCFNodeArrayPartition) return;
postAllChangedDelta();
}
void onMemoryMapChanged() {
value.reset();
type.reset();
type_name.reset();
string.reset();
children.onMemoryMapChanged();
if (parent instanceof TCFNodeExpression) return;
if (parent instanceof TCFNodeArrayPartition) return;
postAllChangedDelta();
}
void onValueChanged() {
value.reset();
type.reset();
type_name.reset();
string.reset();
children.onValueChanged();
postAllChangedDelta();
}
public void onCastToTypeChanged() {
expression.cancel();
value.cancel();
type.cancel();
type_name.cancel();
string.cancel();
expression_text.cancel();
children.onCastToTypeChanged();
postAllChangedDelta();
}
public String getScript() {
return script;
}
String getFieldID() {
return field_id;
}
String getRegisterID() {
return reg_id;
}
int getIndex() {
return index;
}
boolean isDeref() {
return deref;
}
void setSortPosition(int sort_pos) {
this.sort_pos = sort_pos;
}
void setEnabled(boolean enabled) {
if (this.enabled == enabled) return;
this.enabled = enabled;
postAllChangedDelta();
}
/**
* Get expression properties cache.
* The cache is empty is the expression does not represent a variable.
* @return The expression properties cache.
*/
public TCFDataCache<IExpressions.Expression> getVariable() {
return var_expression;
}
/**
* Get expression value cache.
* @return The expression value cache.
*/
public TCFDataCache<IExpressions.Value> getValue() {
return value;
}
/**
* Get expression type cache.
* @return The expression type cache.
*/
public TCFDataCache<ISymbols.Symbol> getType() {
return type;
}
/**
* Get human readable expression script,
* including type cast, and using variable names instead of IDs.
*/
public TCFDataCache<String> getExpressionText() {
return expression_text;
}
private Boolean usePrevValue(Runnable done) {
// Check if view should show old value.
// Old value is shown if context is running or
// stack trace does not contain expression parent frame.
// Return null if waiting for cache update.
if (prev_value == null) return false;
if (parent instanceof TCFNodeStackFrame) {
TCFNodeExecContext exe = (TCFNodeExecContext)parent.parent;
TCFDataCache<TCFContextState> state_cache = exe.getState();
if (!state_cache.validate(done)) return null;
TCFContextState state = state_cache.getData();
if (state == null || !state.is_suspended) return true;
TCFChildrenStackTrace stack_trace_cache = exe.getStackTrace();
if (!stack_trace_cache.validate(done)) return null;
if (stack_trace_cache.getData().get(parent.id) == null) return true;
}
else if (parent instanceof TCFNodeExecContext) {
TCFNodeExecContext exe = (TCFNodeExecContext)parent;
TCFDataCache<TCFContextState> state_cache = exe.getState();
if (!state_cache.validate(done)) return null;
TCFContextState state = state_cache.getData();
if (state == null || !state.is_suspended) return true;
}
return false;
}
private String getTypeName(ISymbols.TypeClass type_class, int size) {
String s = null;
switch (type_class) {
case integer:
if (size == 0) s = "<Void>";
else s = "<Integer-" + (size * 8) + ">";
break;
case cardinal:
if (size == 0) s = "<Void>";
else s = "<Unsigned-" + (size * 8) + ">";
break;
case real:
if (size == 0) s = "<Void>";
else s = "<Float-" + (size * 8) + ">";
break;
}
return s;
}
private boolean getTypeName(StringBuffer bf, TCFDataCache<ISymbols.Symbol> type_cache, Runnable done) {
String name = null;
for (;;) {
String s = null;
boolean get_base_type = false;
if (!type_cache.validate(done)) return false;
ISymbols.Symbol type_symbol = type_cache.getData();
if (type_symbol != null) {
int flags = type_symbol.getFlags();
s = type_symbol.getName();
if (s != null && type_symbol.getTypeClass() == ISymbols.TypeClass.composite) {
if ((flags & ISymbols.SYM_FLAG_UNION_TYPE) != 0) s = "union " + s;
else if ((flags & ISymbols.SYM_FLAG_CLASS_TYPE) != 0) s = "class " + s;
else if ((flags & ISymbols.SYM_FLAG_INTERFACE_TYPE) != 0) s = "interface " + s;
else s = "struct " + s;
}
if (s == null) s = getTypeName(type_symbol.getTypeClass(), type_symbol.getSize());
if (s == null) {
switch (type_symbol.getTypeClass()) {
case pointer:
s = "*";
if ((flags & ISymbols.SYM_FLAG_REFERENCE) != 0) s = "&";
get_base_type = true;
break;
case array:
s = "[" + type_symbol.getLength() + "]";
get_base_type = true;
break;
case composite:
s = "<Structure>";
break;
case function:
{
TCFDataCache<String[]> children_cache = model.getSymbolChildrenCache(type_symbol.getID());
if (!children_cache.validate(done)) return false;
if (children_cache.getError() == null) {
String[] children = children_cache.getData();
if (children != null) {
StringBuffer args = new StringBuffer();
if (name != null) {
args.append('(');
args.append(name);
args.append(')');
name = null;
}
args.append('(');
for (String id : children) {
if (id != children[0]) args.append(',');
if (!getTypeName(args, model.getSymbolInfoCache(id), done)) return false;
}
args.append(')');
s = args.toString();
get_base_type = true;
break;
}
}
}
s = "<Function>";
break;
}
}
if (s != null) {
if ((flags & ISymbols.SYM_FLAG_VOLATILE_TYPE) != 0) s = "volatile " + s;
if ((flags & ISymbols.SYM_FLAG_CONST_TYPE) != 0) s = "const " + s;
}
}
if (s == null) {
name = "N/A";
break;
}
if (name == null) name = s;
else if (!get_base_type) name = s + " " + name;
else name = s + name;
if (!get_base_type) break;
type_cache = model.getSymbolInfoCache(type_symbol.getBaseTypeID());
if (type_cache == null) {
name = "N/A";
break;
}
}
bf.append(name);
return true;
}
private String toASCIIString(byte[] data, int offs, int size, char quote_char) {
StringBuffer bf = new StringBuffer();
bf.append(quote_char);
for (int i = 0; i < size; i++) {
int ch = data[offs + i] & 0xff;
if (ch >= ' ' && ch < 0x7f) {
bf.append((char)ch);
}
else {
switch (ch) {
case '\r': bf.append("\\r"); break;
case '\n': bf.append("\\n"); break;
case '\b': bf.append("\\b"); break;
case '\t': bf.append("\\t"); break;
case '\f': bf.append("\\f"); break;
default:
bf.append('\\');
bf.append((char)('0' + ch / 64));
bf.append((char)('0' + ch / 8 % 8));
bf.append((char)('0' + ch % 8));
}
}
}
if (data.length <= offs + size || data[offs + size] == 0) bf.append(quote_char);
else bf.append("...");
return bf.toString();
}
private String toNumberString(int radix, ISymbols.TypeClass t, byte[] data, int offs, int size, boolean big_endian) {
if (size <= 0 || size > 16) return "";
if (radix != 16) {
switch (t) {
case array:
case composite:
return "";
}
}
String s = null;
if (data == null) s = "N/A";
if (s == null && radix == 10) {
if (t != null) {
switch (t) {
case integer:
s = TCFNumberFormat.toBigInteger(data, offs, size, big_endian, true).toString();
break;
case real:
s = TCFNumberFormat.toFPString(data, offs, size, big_endian);
break;
}
}
}
if (s == null) {
s = TCFNumberFormat.toBigInteger(data, offs, size, big_endian, false).toString(radix);
switch (radix) {
case 8:
if (!s.startsWith("0")) s = "0" + s;
break;
case 16:
if (s.length() < size * 2) {
StringBuffer bf = new StringBuffer();
while (bf.length() + s.length() < size * 2) bf.append('0');
bf.append(s);
s = bf.toString();
}
break;
}
}
if (s == null) s = "N/A";
return s;
}
private String toNumberString(int radix) {
String s = null;
IExpressions.Value val = value.getData();
if (val != null) {
byte[] data = val.getValue();
if (data != null) {
ISymbols.TypeClass t = val.getTypeClass();
if (t == ISymbols.TypeClass.unknown && type.getData() != null) t = type.getData().getTypeClass();
s = toNumberString(radix, t, data, 0, data.length, val.isBigEndian());
}
}
if (s == null) s = "N/A";
return s;
}
private void setLabel(ILabelUpdate result, String name, int col, int radix) {
String s = toNumberString(radix);
if (name == null) {
result.setLabel(s, col);
}
else {
result.setLabel(name + " = " + s, col);
}
}
private boolean isValueChanged(IExpressions.Value x, IExpressions.Value y) {
if (x == null || y == null) return false;
byte[] xb = x.getValue();
byte[] yb = y.getValue();
if (xb == null || yb == null) return false;
if (xb.length != yb.length) return true;
for (int i = 0; i < xb.length; i++) {
if (xb[i] != yb[i]) return true;
}
return false;
}
@Override
protected boolean getData(ILabelUpdate result, Runnable done) {
if (enabled || script == null) {
TCFDataCache<ISymbols.Symbol> field = model.getSymbolInfoCache(field_id);
TCFDataCache<?> pending = null;
if (field != null && !field.validate()) pending = field;
if (reg_id != null && !expression_text.validate(done)) pending = expression_text;
if (!var_expression.validate()) pending = var_expression;
if (!base_text.validate()) pending = base_text;
if (!value.validate()) pending = value;
if (!type.validate()) pending = type;
if (pending != null) {
pending.wait(done);
return false;
}
String name = null;
if (index >= 0) {
if (index == 0 && deref) {
name = "*";
}
else {
name = "[" + index + "]";
}
}
if (name == null && field != null && field.getData() != null) name = field.getData().getName();
if (name == null && reg_id != null && expression_text.getData() != null) name = expression_text.getData();
if (name == null && var_expression.getData() != null) {
TCFDataCache<ISymbols.Symbol> var = model.getSymbolInfoCache(var_expression.getData().getSymbolID());
if (var != null) {
if (!var.validate(done)) return false;
ISymbols.Symbol var_data = var.getData();
if (var_data != null) {
name = var_data.getName();
if (name == null && var_data.getFlag(ISymbols.SYM_FLAG_VARARG)) name = "<VarArg>";
if (name == null) name = "<" + var_data.getID() + ">";
}
}
}
if (name == null && base_text.getData() != null) name = base_text.getData();
if (name != null) {
String cast = model.getCastToType(id);
if (cast != null) name = "(" + cast + ")(" + name + ")";
}
Throwable error = base_text.getError();
if (error == null) error = value.getError();
String[] cols = result.getColumnIds();
if (error != null) {
if (cols == null || cols.length <= 1) {
result.setForeground(rgb_error, 0);
result.setLabel(name + ": N/A", 0);
}
else {
for (int i = 0; i < cols.length; i++) {
String c = cols[i];
if (c.equals(TCFColumnPresentationExpression.COL_NAME)) {
result.setLabel(name, i);
}
else if (c.equals(TCFColumnPresentationExpression.COL_TYPE)) {
if (!type_name.validate(done)) return false;
result.setLabel(type_name.getData(), i);
}
else {
result.setForeground(rgb_error, i);
result.setLabel("N/A", i);
}
}
}
}
else {
if (cols == null) {
setLabel(result, name, 0, 16);
}
else {
for (int i = 0; i < cols.length; i++) {
String c = cols[i];
if (c.equals(TCFColumnPresentationExpression.COL_NAME)) {
result.setLabel(name, i);
}
else if (c.equals(TCFColumnPresentationExpression.COL_TYPE)) {
if (!type_name.validate(done)) return false;
result.setLabel(type_name.getData(), i);
}
else if (c.equals(TCFColumnPresentationExpression.COL_HEX_VALUE)) {
setLabel(result, null, i, 16);
}
else if (c.equals(TCFColumnPresentationExpression.COL_DEC_VALUE)) {
setLabel(result, null, i, 10);
}
}
}
}
next_value = value.getData();
if (isValueChanged(prev_value, next_value)) {
result.setBackground(rgb_highlight, 0);
if (cols != null) {
for (int i = 1; i < cols.length; i++) {
result.setBackground(rgb_highlight, i);
}
}
}
ISymbols.TypeClass type_class = ISymbols.TypeClass.unknown;
ISymbols.Symbol type_symbol = type.getData();
if (type_symbol != null) {
type_class = type_symbol.getTypeClass();
}
switch (type_class) {
case pointer:
result.setImageDescriptor(ImageCache.getImageDescriptor(ImageCache.IMG_VARIABLE_POINTER), 0);
break;
case composite:
case array:
result.setImageDescriptor(ImageCache.getImageDescriptor(ImageCache.IMG_VARIABLE_AGGREGATE), 0);
break;
default:
result.setImageDescriptor(ImageCache.getImageDescriptor(ImageCache.IMG_VARIABLE), 0);
}
}
else {
String[] cols = result.getColumnIds();
if (cols == null || cols.length <= 1) {
result.setForeground(rgb_disabled, 0);
result.setLabel(script, 0);
}
else {
for (int i = 0; i < cols.length; i++) {
String c = cols[i];
if (c.equals(TCFColumnPresentationExpression.COL_NAME)) {
result.setForeground(rgb_disabled, i);
result.setLabel(script, i);
}
}
}
}
return true;
}
private void appendErrorText(StringBuffer bf, Throwable error) {
if (error == null) return;
bf.append("Exception: ");
bf.append(TCFModel.getErrorMessage(error, true));
}
private boolean appendArrayValueText(StyledStringBuffer bf, int level, ISymbols.Symbol type,
byte[] data, int offs, int size, boolean big_endian, Runnable done) {
assert offs + size <= data.length;
int length = type.getLength();
bf.append('[');
if (length > 0) {
int elem_size = size / length;
for (int n = 0; n < length; n++) {
if (n >= 100) {
bf.append("...");
break;
}
if (n > 0) bf.append(", ");
if (!appendValueText(bf, level + 1, type.getBaseTypeID(),
data, offs + n * elem_size, elem_size, big_endian, done)) return false;
}
}
bf.append(']');
return true;
}
private boolean appendCompositeValueText(StyledStringBuffer bf, int level, ISymbols.Symbol type,
byte[] data, int offs, int size, boolean big_endian, Runnable done) {
TCFDataCache<String[]> children_cache = model.getSymbolChildrenCache(type.getID());
if (children_cache == null) {
bf.append("...");
return true;
}
if (!children_cache.validate(done)) return false;
String[] children_data = children_cache.getData();
if (children_data == null) {
bf.append("...");
return true;
}
int cnt = 0;
TCFDataCache<?> pending = null;
for (String id : children_data) {
TCFDataCache<ISymbols.Symbol> field_cache = model.getSymbolInfoCache(id);
if (!field_cache.validate()) {
pending = field_cache;
continue;
}
ISymbols.Symbol field_data = field_cache.getData();
if (field_data == null) continue;
if (field_data.getSymbolClass() != ISymbols.SymbolClass.reference) continue;
String name = field_data.getName();
int f_offs = field_data.getOffset();
int f_size = field_data.getSize();
if (name == null) {
// Super-class members
if (offs + f_offs + f_size > data.length) {
continue;
}
else {
StyledStringBuffer bf1 = new StyledStringBuffer();
if (!appendCompositeValueText(bf1, level, field_data, data,
offs + f_offs, f_size, big_endian, done)) return false;
if (bf1.length() > 0) {
if (cnt > 0) bf.append(", ");
bf.append(bf1);
cnt++;
}
}
}
else {
if (cnt > 0) bf.append(", ");
bf.append(name);
bf.append('=');
if (offs + f_offs + f_size > data.length) {
bf.append('?');
}
else {
if (!appendValueText(bf, level + 1, field_data.getTypeID(),
data, offs + f_offs, f_size, big_endian, done)) return false;
}
cnt++;
}
}
if (pending == null) return true;
pending.wait(done);
return false;
}
private boolean appendValueText(StyledStringBuffer bf, int level, String type_id,
byte[] data, int offs, int size, boolean big_endian, Runnable done) {
if (data == null) return true;
ISymbols.Symbol type_data = null;
if (type_id != null) {
TCFDataCache<ISymbols.Symbol> type_cache = model.getSymbolInfoCache(type_id);
if (!type_cache.validate(done)) return false;
type_data = type_cache.getData();
}
if (type_data == null) {
ISymbols.TypeClass type_class = ISymbols.TypeClass.unknown;
if (!value.validate(done)) return false;
if (value.getData() != null) type_class = value.getData().getTypeClass();
if (level == 0) {
assert offs == 0;
assert size == data.length;
String s = getTypeName(type_class, size);
if (s == null) s = "not available";
bf.append("Type: ", SWT.BOLD);
bf.append(s);
bf.append('\n');
bf.append("Size: ", SWT.BOLD);
bf.append(size);
bf.append(size == 1 ? " byte\n" : " bytes\n");
if (size > 0) {
if (type_class == ISymbols.TypeClass.integer || type_class == ISymbols.TypeClass.real) {
bf.append("Dec: ", SWT.BOLD);
bf.append(toNumberString(10, type_class, data, offs, size, big_endian));
bf.append("\n");
}
bf.append("Hex: ", SWT.BOLD);
bf.append(toNumberString(16, type_class, data, offs, size, big_endian));
bf.append("\n");
}
}
else if (type_class == ISymbols.TypeClass.integer || type_class == ISymbols.TypeClass.real) {
bf.append(toNumberString(10, type_class, data, offs, size, big_endian));
}
else {
bf.append(toNumberString(16, type_class, data, offs, size, big_endian));
}
return true;
}
if (level == 0) {
if (!string.validate(done)) return false;
Throwable e = string.getError();
String s = string.getData();
if (s != null) {
bf.append(s);
bf.append("\n");
}
else if (e != null) {
bf.append("Cannot read pointed value: ", SWT.BOLD, null, rgb_error);
bf.append(TCFModel.getErrorMessage(e, true), SWT.ITALIC, null, rgb_error);
}
}
if (type_data.getSize() > 0) {
ISymbols.TypeClass type_class = type_data.getTypeClass();
switch (type_class) {
case enumeration:
case integer:
case cardinal:
case real:
if (level == 0) {
bf.append("Dec: ", SWT.BOLD);
bf.append(toNumberString(10, type_class, data, offs, size, big_endian));
bf.append("\n");
bf.append("Oct: ", SWT.BOLD);
bf.append(toNumberString(8, type_class, data, offs, size, big_endian));
bf.append("\n");
bf.append("Hex: ", SWT.BOLD);
bf.append(toNumberString(16, type_class, data, offs, size, big_endian));
bf.append("\n");
}
else if (type_data.getTypeClass() == ISymbols.TypeClass.cardinal) {
bf.append("0x");
bf.append(toNumberString(16, type_class, data, offs, size, big_endian));
}
else {
bf.append(toNumberString(10, type_class, data, offs, size, big_endian));
}
break;
case pointer:
case function:
if (level == 0) {
bf.append("Oct: ", SWT.BOLD);
bf.append(toNumberString(8, type_class, data, offs, size, big_endian));
bf.append("\n");
bf.append("Hex: ", SWT.BOLD);
bf.append(toNumberString(16, type_class, data, offs, size, big_endian));
bf.append("\n");
}
else {
bf.append("0x");
bf.append(toNumberString(16, type_class, data, offs, size, big_endian));
}
break;
case array:
if (!appendArrayValueText(bf, level, type_data, data, offs, size, big_endian, done)) return false;
if (level == 0) bf.append("\n");
break;
case composite:
bf.append('{');
if (!appendCompositeValueText(bf, level, type_data, data, offs, size, big_endian, done)) return false;
bf.append('}');
if (level == 0) bf.append("\n");
break;
}
}
if (level == 0) {
if (!type_name.validate(done)) return false;
String nm = type_name.getData();
if (nm != null) {
bf.append("Type: ", SWT.BOLD);
bf.append(nm);
bf.append("\n");
}
bf.append("Size: ", SWT.BOLD);
bf.append(type_data.getSize());
bf.append(type_data.getSize() == 1 ? " byte\n" : " bytes\n");
}
return true;
}
private String getRegisterName(String reg_id, Runnable done) {
String name = reg_id;
TCFDataCache<?> pending = null;
TCFNodeRegister reg_node = null;
LinkedList<TCFChildren> queue = new LinkedList<TCFChildren>();
TCFNode n = parent;
while (n != null) {
if (n instanceof TCFNodeStackFrame) {
queue.add(((TCFNodeStackFrame)n).getRegisters());
}
if (n instanceof TCFNodeExecContext) {
queue.add(((TCFNodeExecContext)n).getRegisters());
break;
}
n = n.parent;
}
while (!queue.isEmpty()) {
TCFChildren reg_list = queue.removeFirst();
if (!reg_list.validate()) {
pending = reg_list;
}
else {
Map<String,TCFNode> reg_map = reg_list.getData();
if (reg_map != null) {
reg_node = (TCFNodeRegister)reg_map.get(reg_id);
if (reg_node != null) break;
for (TCFNode node : reg_map.values()) {
queue.add(((TCFNodeRegister)node).getChildren());
}
}
}
}
if (pending != null) {
pending.wait(done);
return null;
}
if (reg_node != null) {
TCFDataCache<IRegisters.RegistersContext> reg_ctx_cache = reg_node.getContext();
if (!reg_ctx_cache.validate(done)) return null;
IRegisters.RegistersContext reg_ctx_data = reg_ctx_cache.getData();
if (reg_ctx_data != null && reg_ctx_data.getName() != null) name = reg_ctx_data.getName();
}
return name;
}
public boolean getDetailText(StyledStringBuffer bf, Runnable done) {
if (!enabled) {
bf.append("Disabled");
return true;
}
if (!expression.validate(done)) return false;
if (!value.validate(done)) return false;
int pos = bf.length();
appendErrorText(bf.getStringBuffer(), expression.getError());
if (bf.length() == pos) appendErrorText(bf.getStringBuffer(), value.getError());
if (bf.length() > pos) {
bf.append(pos, 0, null, rgb_error);
}
else {
IExpressions.Value v = value.getData();
if (v != null) {
byte[] data = v.getValue();
if (data != null) {
boolean big_endian = v.isBigEndian();
if (!appendValueText(bf, 0, v.getTypeID(),
data, 0, data.length, big_endian, done)) return false;
}
String reg_id = v.getRegisterID();
if (reg_id != null) {
String nm = getRegisterName(reg_id, done);
if (nm == null) return false;
bf.append("Register: ", SWT.BOLD);
bf.append(nm);
bf.append('\n');
}
Number addr = v.getAddress();
if (addr != null) {
BigInteger i = JSON.toBigInteger(addr);
bf.append("Address: ", SWT.BOLD);
bf.append("0x");
bf.append(i.toString(16));
bf.append('\n');
}
}
}
return true;
}
public String getValueText(boolean add_error_text, Runnable done) {
if (!expression.validate(done)) return null;
if (!value.validate(done)) return null;
StyledStringBuffer bf = new StyledStringBuffer();
IExpressions.Value v = value.getData();
if (v != null) {
byte[] data = v.getValue();
if (data != null) {
boolean big_endian = v.isBigEndian();
if (!appendValueText(bf, 1, v.getTypeID(),
data, 0, data.length, big_endian, done)) return null;
}
}
if (add_error_text) {
if (bf.length() == 0 && expression.getError() != null) {
bf.append(TCFModel.getErrorMessage(expression.getError(), false));
}
if (bf.length() == 0 && value.getError() != null) {
bf.append(TCFModel.getErrorMessage(value.getError(), false));
}
}
return bf.toString();
}
@Override
protected boolean getData(IChildrenCountUpdate result, Runnable done) {
if (enabled) {
if (!children.validate(done)) return false;
result.setChildCount(children.size());
}
else {
result.setChildCount(0);
}
return true;
}
@Override
protected boolean getData(IChildrenUpdate result, Runnable done) {
if (!enabled) return true;
return children.getData(result, done);
}
@Override
protected boolean getData(IHasChildrenUpdate result, Runnable done) {
if (enabled) {
if (!children.validate(done)) return false;
result.setHasChilren(children.size() > 0);
}
else {
result.setHasChilren(false);
}
return true;
}
@Override
public int compareTo(TCFNode n) {
TCFNodeExpression e = (TCFNodeExpression)n;
if (sort_pos < e.sort_pos) return -1;
if (sort_pos > e.sort_pos) return +1;
return 0;
}
public CellEditor getCellEditor(IPresentationContext context, String column_id, Object element, Composite parent) {
assert element == this;
if (TCFColumnPresentationExpression.COL_NAME.equals(column_id) && script != null) {
return new TextCellEditor(parent);
}
if (TCFColumnPresentationExpression.COL_HEX_VALUE.equals(column_id)) {
return new TextCellEditor(parent);
}
if (TCFColumnPresentationExpression.COL_DEC_VALUE.equals(column_id)) {
return new TextCellEditor(parent);
}
return null;
}
private static final ICellModifier cell_modifier = new ICellModifier() {
public boolean canModify(Object element, final String property) {
final TCFNodeExpression node = (TCFNodeExpression)element;
return new TCFTask<Boolean>() {
public void run() {
if (TCFColumnPresentationExpression.COL_NAME.equals(property)) {
done(node.script != null);
return;
}
if (node.enabled) {
if (!node.expression.validate(this)) return;
if (node.expression.getData() != null && node.expression.getData().expression.canAssign()) {
if (!node.value.validate(this)) return;
if (!node.type.validate(this)) return;
if (TCFColumnPresentationExpression.COL_HEX_VALUE.equals(property)) {
done(TCFNumberFormat.isValidHexNumber(node.toNumberString(16)) == null);
return;
}
if (TCFColumnPresentationExpression.COL_DEC_VALUE.equals(property)) {
done(TCFNumberFormat.isValidDecNumber(true, node.toNumberString(10)) == null);
return;
}
}
}
done(Boolean.FALSE);
}
}.getE();
}
public Object getValue(Object element, final String property) {
final TCFNodeExpression node = (TCFNodeExpression)element;
return new TCFTask<String>() {
public void run() {
if (TCFColumnPresentationExpression.COL_NAME.equals(property)) {
done(node.script);
return;
}
if (!node.value.validate(this)) return;
if (node.value.getData() != null) {
if (TCFColumnPresentationExpression.COL_HEX_VALUE.equals(property)) {
done(node.toNumberString(16));
return;
}
if (TCFColumnPresentationExpression.COL_DEC_VALUE.equals(property)) {
done(node.toNumberString(10));
return;
}
}
done(null);
}
}.getE();
}
public void modify(Object element, final String property, final Object value) {
if (value == null) return;
final TCFNodeExpression node = (TCFNodeExpression)element;
new TCFTask<Boolean>() {
public void run() {
try {
if (TCFColumnPresentationExpression.COL_NAME.equals(property)) {
if (!node.script.equals(value)) {
IExpressionManager m = DebugPlugin.getDefault().getExpressionManager();
for (final IExpression e : m.getExpressions()) {
if (node.script.equals(e.getExpressionText())) m.removeExpression(e);
}
IExpression e = m.newWatchExpression((String)value);
m.addExpression(e);
}
done(Boolean.TRUE);
return;
}
if (!node.expression.validate(this)) return;
if (node.expression.getData() != null) {
IExpressions.Expression exp = node.expression.getData().expression;
if (exp.canAssign()) {
byte[] bf = null;
int size = exp.getSize();
boolean is_float = false;
boolean big_endian = false;
boolean signed = false;
if (!node.value.validate(this)) return;
IExpressions.Value eval = node.value.getData();
if (eval != null) {
switch(eval.getTypeClass()) {
case real:
is_float = true;
case integer:
signed = true;
break;
}
big_endian = eval.isBigEndian();
size = eval.getValue().length;
}
String input = (String)value;
String error = null;
if (TCFColumnPresentationExpression.COL_HEX_VALUE.equals(property)) {
error = TCFNumberFormat.isValidHexNumber(input);
if (error == null) bf = TCFNumberFormat.toByteArray(input, 16, false, size, signed, big_endian);
}
else if (TCFColumnPresentationExpression.COL_DEC_VALUE.equals(property)) {
error = TCFNumberFormat.isValidDecNumber(is_float, input);
if (error == null) bf = TCFNumberFormat.toByteArray(input, 10, is_float, size, signed, big_endian);
}
if (error != null) throw new Exception("Invalid value: " + value, new Exception(error));
if (bf != null) {
IExpressions exps = node.launch.getService(IExpressions.class);
exps.assign(exp.getID(), bf, new IExpressions.DoneAssign() {
public void doneAssign(IToken token, Exception error) {
node.getRootExpression().onValueChanged();
if (error != null) {
node.model.showMessageBox("Cannot modify element value", error);
done(Boolean.FALSE);
}
else {
done(Boolean.TRUE);
}
}
});
return;
}
}
}
done(Boolean.FALSE);
}
catch (Throwable x) {
node.model.showMessageBox("Cannot modify element value", x);
done(Boolean.FALSE);
}
}
}.getE();
}
};
public ICellModifier getCellModifier(IPresentationContext context, Object element) {
assert element == this;
return cell_modifier;
}
@SuppressWarnings("rawtypes")
@Override
public Object getAdapter(Class adapter) {
if (script != null) {
if (adapter == IExpression.class) {
IExpressionManager m = DebugPlugin.getDefault().getExpressionManager();
for (final IExpression e : m.getExpressions()) {
if (script.equals(e.getExpressionText())) return e;
}
}
if (adapter == IWatchExpression.class) {
IExpressionManager m = DebugPlugin.getDefault().getExpressionManager();
for (final IExpression e : m.getExpressions()) {
if (e instanceof IWatchExpression && script.equals(e.getExpressionText())) return e;
}
}
}
return super.getAdapter(adapter);
}
}