/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package flex.tools.debugger.cli; import java.util.StringTokenizer; import java.util.Vector; import flash.tools.debugger.Isolate; import flash.tools.debugger.PlayerDebugException; import flash.tools.debugger.Session; import flash.tools.debugger.SessionManager; import flash.tools.debugger.Value; import flash.tools.debugger.ValueAttribute; import flash.tools.debugger.Variable; import flash.tools.debugger.VariableType; import flash.tools.debugger.concrete.DValue; import flash.tools.debugger.events.ExceptionFault; import flash.tools.debugger.events.FaultEvent; import flash.tools.debugger.expression.Context; import flash.tools.debugger.expression.ExpressionEvaluatorException; import flash.tools.debugger.expression.NoSuchVariableException; import flash.tools.debugger.expression.PlayerFaultException; public class ExpressionContext implements Context { ExpressionCache m_cache; Object m_current; boolean m_createIfMissing; // set if we need to create a variable if it doesn't exist Vector<String> m_namedPath; boolean m_nameLocked; String m_newline = System.getProperty("line.separator"); //$NON-NLS-1$ int m_isolateId; // used when evaluating an expression public ExpressionContext(ExpressionCache cache) { m_cache = cache; m_current = null; m_createIfMissing = false; m_namedPath = new Vector<String>(); m_nameLocked = false; m_isolateId = Isolate.DEFAULT_ID; } public void setIsolateId(int id) { m_isolateId = id; } void setContext(Object o) { m_current = o; } void pushName(String name) { if (m_nameLocked || name.length() < 1) return; m_namedPath.add(name); } boolean setName(String name) { if (m_nameLocked) return true; m_namedPath.clear(); pushName(name); return true; } void lockName() { m_nameLocked = true; } public String getName() { int size = m_namedPath.size(); StringBuilder sb = new StringBuilder(); for(int i=0; i<size; i++) { String s = m_namedPath.get(i); if (i > 0) sb.append('.'); sb.append(s); } return ( sb.toString() ); } String getCurrentPackageName() { String s = null; try { Integer o = (Integer)m_cache.get(DebugCLI.LIST_MODULE); s = m_cache.getPackageName(o.intValue()); } catch(NullPointerException npe) { } catch(ClassCastException cce) { } return s; } // // // Start of Context API implementation // // public void createPseudoVariables(boolean oui) { m_createIfMissing = oui; } // create a new context object by combining the current one and o public Context createContext(Object o) { ExpressionContext c = new ExpressionContext(m_cache); c.setContext(o); c.createPseudoVariables(m_createIfMissing); c.m_namedPath.addAll(m_namedPath); c.setIsolateId(m_isolateId); return c; } // assign the object o, the value v public void assign(Object o, Value v) throws NoSuchVariableException, PlayerFaultException { try { // first see if it is an internal property (avoids player calls) InternalProperty prop = resolveToInternalProperty(o); // we expect that o is a variable that can be resolved or is a specially marked internal variable if (prop != null) { assignInternal(prop, v); } else { boolean wasCreateIfMissing = m_createIfMissing; createPseudoVariables(true); Variable var = null; try { var = resolveToVariable(o); } finally { createPseudoVariables(wasCreateIfMissing); } if (var == null) throw new NoSuchVariableException(m_current); // set the value, for the case of a variable that does not exist it will not have a type // so we try to glean one from v. FaultEvent faultEvent = var.setValue(getSession(), v.getType(), v.getValueAsString()); if (faultEvent != null) throw new PlayerFaultException(faultEvent); } } catch(PlayerDebugException pde) { throw new ExpressionEvaluatorException(pde); } } /** * The Context interface which goes out and gets values from the session * Expressions use this interface as a means of evaluation. * * We also use this to create a reference to internal variables. */ public Object lookup(Object o) throws NoSuchVariableException, PlayerFaultException { Object result = null; try { // first see if it is an internal property (avoids player calls) if ( (result = resolveToInternalProperty(o)) != null) ; // attempt to resolve to a player variable else if ( (result = resolveToVariable(o)) != null) ; // or value else if ( (result = resolveToValue(o)) != null) ; else throw new NoSuchVariableException(o); // take on the path to the variable; so 'what' command prints something nice if (result instanceof VariableFacade) { ((VariableFacade)result).setPath(getName()); } // if the attempt to get the variable's value threw an exception inside the // player (most likely because the variable is actually a getter, and the // getter threw something), then throw something here Value resultValue = null; if (result instanceof Variable) { if (result instanceof VariableFacade && ((VariableFacade)result).getVariable() == null) resultValue = null; else resultValue = ((Variable)result).getValue(); } else if (result instanceof Value) { resultValue = (Value) result; } if (resultValue != null) { if (resultValue.isAttributeSet(ValueAttribute.IS_EXCEPTION)) { String value = resultValue.getValueAsString(); throw new PlayerFaultException(new ExceptionFault(value, false, resultValue, resultValue.getIsolateId())); } } } catch(PlayerDebugException pde) { result = Value.UNDEFINED; } return result; } /* returns a string consisting of formatted member names and values */ public Object lookupMembers(Object o) throws NoSuchVariableException { Variable var = null; Value val = null; Variable[] mems = null; try { var = resolveToVariable(o); if (var != null) val = var.getValue(); else val = resolveToValue(o); mems = val.getMembers(getSession()); } catch(NullPointerException npe) { throw new NoSuchVariableException(o); } catch(PlayerDebugException pde) { throw new NoSuchVariableException(o); // not quite right... } StringBuilder sb = new StringBuilder(); if (var != null) m_cache.appendVariable(sb, var, m_isolateId); else m_cache.appendVariableValue(sb, val, m_isolateId); boolean attrs = m_cache.propertyEnabled(DebugCLI.DISPLAY_ATTRIBUTES); if (attrs && var != null) ExpressionCache.appendVariableAttributes(sb, var); // [mmorearty] experimenting with hierarchical display of members String[] classHierarchy = val.getClassHierarchy(false); if (classHierarchy != null && getSession().getPreference(SessionManager.PREF_HIERARCHICAL_VARIABLES) != 0) { for (int c=0; c<classHierarchy.length; ++c) { String classname = classHierarchy[c]; sb.append(m_newline + "(Members of " + classname + ")"); //$NON-NLS-1$ //$NON-NLS-2$ for (int i=0; i<mems.length; ++i) { if (classname.equals(mems[i].getDefiningClass())) { sb.append(m_newline + " "); //$NON-NLS-1$ m_cache.appendVariable(sb, mems[i], m_isolateId); if (attrs) ExpressionCache.appendVariableAttributes(sb, mems[i]); } } } } else { for(int i=0; i<mems.length; i++) { sb.append(m_newline + " "); //$NON-NLS-1$ m_cache.appendVariable(sb, mems[i], m_isolateId); if (attrs) ExpressionCache.appendVariableAttributes(sb, mems[i]); } } return sb.toString(); } // // // End of Context API implementation // // // used to assign a value to an internal variable private void assignInternal(InternalProperty var, Value v) throws NoSuchVariableException, NumberFormatException, PlayerDebugException { // otherwise set it if (v.getType() != VariableType.NUMBER) throw new NumberFormatException(v.getValueAsString()); long l = Long.parseLong(v.getValueAsString()); m_cache.put(var.getName(), (int)l); } InternalProperty resolveToInternalProperty(Object o) { if (o instanceof String && ((String)o).charAt(0) == '$') { String key = (String)o; Object value = null; try { value = m_cache.get(key); } catch(Exception e) {} return new InternalProperty(key, value); } return null; } /** * Resolve the object into a variable by various means and * using the current context. * @return variable, or <code>null</code> */ Variable resolveToVariable(Object o) throws PlayerDebugException { Variable v = null; // if o is a variable already, then we're done! if (o instanceof Variable) return (Variable)o; /** * Resolve the name to something */ { // not an id so try as name String name = o.toString(); long id = nameAsId(name); /** * if #N was used just pick up the variable, otherwise * we need to use the current context to resolve * the name to a member */ if (id != Value.UNKNOWN_ID) { // TODO what here? } else { // try to resolve as a member of current context (will set context if null) id = determineContext(name); v = locateForNamed(id, name, true); if (v != null) v = new VariableFacade(v, id, m_isolateId); else if (m_createIfMissing && name.charAt(0) != '$') v = new VariableFacade(id, name, m_isolateId); } } /* return the variable */ return v; } /* * Resolve the object into a variable by various means and * using the current context. */ Value resolveToValue(Object o) throws PlayerDebugException { Value v = null; // if o is a variable or a value already, then we're done! if (o instanceof Value) return (Value)o; else if (o instanceof Variable) return ((Variable)o).getValue(); else if (o instanceof InternalProperty) return DValue.forPrimitive(((InternalProperty)o).m_value, m_isolateId); /** * Resolve the name to something */ if (m_current == null) { // not an id so try as name String name = o.toString(); long id = nameAsId(name); /** * if #N was used just pick up the variable, otherwise * we need to use the current context to resolve * the name to a member */ if (id != Value.UNKNOWN_ID) { v = getSession().getWorkerSession(m_isolateId).getValue((int)id); } else if (name.equals("undefined")) //$NON-NLS-1$ { v = DValue.forPrimitive(Value.UNDEFINED, m_isolateId); } else { // Ask the player to find something, anything, on the scope chain // with this name. We'll end up here, for example, when resolving // things like MyClass, String, Number, etc. v = getSession().getWorkerSession(m_isolateId).getGlobal(name); } } /* return the value */ return v; } // special code for #N support. I.e. naming a variable via an ID long nameAsId(String name) { long id = Value.UNKNOWN_ID; try { if (name.charAt(0) == '#') id = Long.parseLong(name.substring(1)); } catch(Exception e) { id = Value.UNKNOWN_ID; } return id; } /** * Using the given id as a parent find the member named * name. * @throws NoSuchVariableException if id is UNKNOWN_ID */ Variable memberNamed(long id, String name) throws NoSuchVariableException, PlayerDebugException { Variable v = null; Value parent = getSession().getWorkerSession(m_isolateId).getValue(id); if (parent == null) throw new NoSuchVariableException(name); /* got a variable now return the member if any */ v = parent.getMemberNamed(getSession(), name); return v; } /** * All the really good stuff about finding where name exists goes here! * * If name is not null, then it implies that we use the existing * m_current to find a member of m_current. If m_current is null * Then we need to probe variable context points attempting to locate * name. When we find a match we set the m_current to this context * * If name is null then we simply return the current context. */ long determineContext(String name) throws PlayerDebugException { long id = Value.UNKNOWN_ID; // have we already resolved our context... if (m_current != null) { id = toValue().getId(); } // nothing to go on, so we're done else if (name == null) ; // use the name and try and resolve where we are... else { // Each stack frame has a root variable under (BASE_ID-depth) // where depth is the depth of the stack. // So we query for our current stack depth and use that // as the context for our base computation long baseId = Value.BASE_ID; int depth = ((Integer)m_cache.get(DebugCLI.DISPLAY_FRAME_NUMBER)).intValue(); baseId -= depth; // obtain data about our current state Variable contextVar = null; Value contextVal = null; Value val = null; // look for 'name' starting from local scope if ( (val = locateParentForNamed(baseId, name, false)) != null) ; // get the this pointer, then look for 'name' starting from that point else if ( ( (contextVar = locateForNamed(baseId, "this", false)) != null ) && //$NON-NLS-1$ ( setName("this") && (val = locateParentForNamed(contextVar.getValue().getId(), name, true)) != null ) ) //$NON-NLS-1$ ; // now try to see if 'name' exists off of _root else if ( setName("_root") && (val = locateParentForNamed(Value.ROOT_ID, name, true)) != null ) //$NON-NLS-1$ ; // now try to see if 'name' exists off of _global else if ( setName("_global") && (val = locateParentForNamed(Value.GLOBAL_ID, name, true)) != null ) //$NON-NLS-1$ ; // now try off of class level, if such a thing can be found else if ( ( (contextVal = locate(Value.GLOBAL_ID, getCurrentPackageName(), false)) != null ) && ( setName("_global."+getCurrentPackageName()) && (val = locateParentForNamed(contextVal.getId(), name, true)) != null ) ) //$NON-NLS-1$ ; // if we found it then stake this as our context! if (val != null) { id = val.getId(); pushName(name); lockName(); } } return id; } /** * Performs a search for a member with the given name using the * given id as the parent variable. * * If a match is found then, we return the parent variable of * the member that matched. The proto chain is optionally traversed. * * No exceptions are thrown */ Value locateParentForNamed(long id, String name, boolean traverseProto) throws PlayerDebugException { StringBuilder sb = new StringBuilder(); Variable var = null; Value val = null; try { var = memberNamed(id, name); // see if we need to traverse the proto chain while (var == null && traverseProto) { // first attempt to get __proto__, then resolve name Variable proto = memberNamed(id, "__proto__"); //$NON-NLS-1$ sb.append("__proto__"); //$NON-NLS-1$ if (proto == null) traverseProto = false; else { id = proto.getValue().getId(); var = memberNamed(id, name); if (var == null) sb.append('.'); } } } catch(NoSuchVariableException nsv) { // don't worry about this one, it means variable with id couldn't be found } catch(NullPointerException npe) { // probably no session } // what we really want is the parent not the child variable if (var != null) { pushName(sb.toString()); val = getSession().getWorkerSession(m_isolateId).getValue(id); } return val; } // variant of locateParentForNamed, whereby we return the child variable Variable locateForNamed(long id, String name, boolean traverseProto) throws PlayerDebugException { Variable var = null; Value v = locateParentForNamed(id, name, traverseProto); if (v != null) { try { var = memberNamed(v.getId(), name); } catch(NoSuchVariableException nse) { v = null; } } return var; } /** * Locates the member via a dotted name starting at the given id. * It will traverse any and all proto chains if necc. to find the name. */ Value locate(long startingId, String dottedName, boolean traverseProto) throws PlayerDebugException { if (dottedName == null) return null; // first rip apart the dottedName StringTokenizer names = new StringTokenizer(dottedName, "."); //$NON-NLS-1$ Value val = getSession().getWorkerSession(m_isolateId).getValue(startingId); while(names.hasMoreTokens() && val != null) val = locateForNamed(val.getId(), names.nextToken(), traverseProto).getValue(); return val; } /* * @see flash.tools.debugger.expression.Context#toValue(java.lang.Object) */ public Value toValue(Object o) { // if o is a variable or a value already, then we're done! if (o instanceof Value) return (Value)o; else if (o instanceof Variable) return ((Variable)o).getValue(); else if (o instanceof InternalProperty) return DValue.forPrimitive(((InternalProperty)o).m_value, m_isolateId); else return DValue.forPrimitive(o, m_isolateId); } public Value toValue() { return toValue(m_current); } public Session getSession() { return m_cache.getSession(); } @Override public int getIsolateId() { return m_isolateId; } }