http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/07f5a7de/debugger/src/main/java/flash/tools/debugger/concrete/DLocation.java ---------------------------------------------------------------------- diff --git a/debugger/src/main/java/flash/tools/debugger/concrete/DLocation.java b/debugger/src/main/java/flash/tools/debugger/concrete/DLocation.java new file mode 100644 index 0000000..01c5ab3 --- /dev/null +++ b/debugger/src/main/java/flash/tools/debugger/concrete/DLocation.java @@ -0,0 +1,73 @@ +/* + * 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 flash.tools.debugger.concrete; + +import flash.tools.debugger.Location; +import flash.tools.debugger.SourceFile; + +public class DLocation implements Location +{ + SourceFile m_source; + int m_line; + int m_isolateId; + boolean m_removed; + + DLocation(SourceFile src, int line, int isolateId) + { + m_source = src; + m_line = line; + m_removed = false; + m_isolateId = isolateId; + } + + /* getters/setters */ + public SourceFile getFile() { return m_source; } + public int getLine() { return m_line; } + public boolean isRemoved() { return m_removed; } + public void setRemoved(boolean removed) { m_removed = removed; } + + public int getId() { return encodeId(getFile().getId(), getLine()); } + + /* encode /decode */ + public static final int encodeId(int fileId, int line) + { + return ( (line << 16) | fileId ); + } + + public static final int decodeFile(long id) + { + return (int)(id & 0xffff); + } + + public static final int decodeLine(long id) + { + return (int)(id >> 16 & 0xffff); + } + + /** for debugging */ + @Override + public String toString() + { + return m_source.toString() + ":" + m_line; //$NON-NLS-1$ + } + + @Override + public int getIsolateId() { + return m_isolateId; + } +}
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/07f5a7de/debugger/src/main/java/flash/tools/debugger/concrete/DManager.java ---------------------------------------------------------------------- diff --git a/debugger/src/main/java/flash/tools/debugger/concrete/DManager.java b/debugger/src/main/java/flash/tools/debugger/concrete/DManager.java new file mode 100644 index 0000000..a8b1c8a --- /dev/null +++ b/debugger/src/main/java/flash/tools/debugger/concrete/DManager.java @@ -0,0 +1,2583 @@ +/* + * 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 flash.tools.debugger.concrete; + +import java.io.InputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import flash.tools.debugger.Isolate; +import flash.tools.debugger.SourceLocator; +import flash.tools.debugger.SuspendReason; +import flash.tools.debugger.SwfInfo; +import flash.tools.debugger.Value; +import flash.tools.debugger.VariableAttribute; +import flash.tools.debugger.VariableType; +import flash.tools.debugger.events.BreakEvent; +import flash.tools.debugger.events.ConsoleErrorFault; +import flash.tools.debugger.events.DebugEvent; +import flash.tools.debugger.events.DivideByZeroFault; +import flash.tools.debugger.events.ExceptionFault; +import flash.tools.debugger.events.FaultEvent; +import flash.tools.debugger.events.FileListModifiedEvent; +import flash.tools.debugger.events.InvalidWithFault; +import flash.tools.debugger.events.IsolateCreateEvent; +import flash.tools.debugger.events.IsolateExitEvent; +import flash.tools.debugger.events.ProtoLimitFault; +import flash.tools.debugger.events.RecursionLimitFault; +import flash.tools.debugger.events.ScriptTimeoutFault; +import flash.tools.debugger.events.StackUnderFlowFault; +import flash.tools.debugger.events.SwfLoadedEvent; +import flash.tools.debugger.events.SwfUnloadedEvent; +import flash.tools.debugger.events.TraceEvent; +import flash.util.Trace; + +/** + * Implements the receiving and updating of debug state from the socket + * connection of the Flash Player. + */ +public class DManager implements DProtocolNotifierIF, SourceLocator { + private final HashMap<String, String> m_parms; + + private final HashMap<Integer, DIsolate> m_isolates; /* + * WARNING: accessed + * from multiple threads + */ + + /** + * The currently active isolate or worker + */ + private Isolate m_activeIsolate = DEFAULT_ISOLATE; + + private LinkedList<DebugEvent> m_event; /* + * our event queue; WARNING: + * accessed from multiple threads + */ + private SourceLocator m_sourceLocator; + + + private boolean m_squelchEnabled; /* + * true if we are talking to a squelch + * enabled debug player + */ + private int m_playerVersion; /* + * player version number obtained from InVersion + * message; e.g. 9 for Flash Player 9.0 + */ + + private boolean m_sourceListModified; /* + * deprecated; indicates m_source has + * changed since last call to + * getSource(). WARNING: lock with + * synchronized (m_source) { ... } + */ + private byte[] m_actions; /* deprecated */ + private String[] m_lastConstantPool; /* deprecated */ + + // SWF/SWD fetching and parsing + private String m_uri; + private byte[] m_swf; // latest swf obtained from get swf + private byte[] m_swd; // latest swd obtained from get swd + + private Map<String, String> m_options = new HashMap<String, String>(); // Player + // options + // that + // have + // been + // queried + // by + // OutGetOption, + // and + // come + // back + // via + // InOption + + public static final String ARGUMENTS_MARKER = "$arguments"; //$NON-NLS-1$ + public static final String SCOPE_CHAIN_MARKER = "$scopechain"; //$NON-NLS-1$ + + private static final DIsolate DEFAULT_ISOLATE = DIsolate.DEFAULT_ISOLATE; + private Isolate m_inIsolate = DEFAULT_ISOLATE; + private final Object m_inIsolateLock; + private final Object m_activeIsolateLock; + private boolean m_wideLines; + private DManagerIsolateState m_mainState; + private HashMap<Integer, DManagerIsolateState> m_isolateState; + + + class DManagerIsolateState { + public DSuspendInfo m_suspendInfo; + public DSwfInfo m_lastSwfInfo; /* + * hack for syncing swfinfo records with + * incoming InScript messages + */ + public DVariable m_lastInGetVariable;/* + * hack for getVariable call to work + * with getters + */ + public boolean m_attachChildren; /* + * hack for getVariable call to work + * with getters + */ + public DVariable m_lastInCallFunction; /* hack for callFunction to work */ + public DVariable m_lastInBinaryOp; + private boolean m_executingPlayerCode; + private FaultEvent m_faultEventDuringPlayerCodeExecution; + + public final ArrayList<DLocation> m_breakpoints; /* + * WARNING: accessed + * from multiple threads + */ + public final Map<Integer, DModule> m_source; /* + * WARNING: accessed from + * multiple threads + */ + private final ArrayList<DSwfInfo> m_swfInfo; /* + * WARNING: accessed from + * multiple threads + */ + private final ArrayList<DWatch> m_watchpoints; /* + * WARNING: accessed + * from multiple threads + */ + + /** + * The currently active stack frames. + */ + public ArrayList<DStackContext> m_frames; + + /** + * The stack frames that were active the last time the player was + * suspended. + */ + public ArrayList<DStackContext> m_previousFrames; + + /** + * A list of all known variables in the player. Stored as a mapping from + * an object's id to its DValue. + */ + public Map<Long, DValue> m_values; + + /** + * A list of all known variables in the player from the previous time + * the player was suspended. Stored as a mapping from an object's id to + * its DValue. + */ + public Map<Long, DValue> m_previousValues; + + public DManagerIsolateState() { + m_source = new HashMap<Integer, DModule>(); + m_values = new HashMap<Long, DValue>(); + m_previousValues = new HashMap<Long, DValue>(); + m_frames = new ArrayList<DStackContext>(); + m_previousFrames = new ArrayList<DStackContext>(); + m_suspendInfo = null; + m_lastInCallFunction = null; + m_breakpoints = new ArrayList<DLocation>(); + m_swfInfo = new ArrayList<DSwfInfo>(); + m_watchpoints = new ArrayList<DWatch>(); + m_suspendInfo = null; + m_lastInGetVariable = null; + m_attachChildren = true; + m_lastInCallFunction = null; + + } + } + + private DManagerIsolateState getIsolateState(int isolateId) { + if (isolateId == Isolate.DEFAULT_ID) + return m_mainState; + + DManagerIsolateState isolateState = null; + if (!m_isolateState.containsKey(isolateId)) { + isolateState = new DManagerIsolateState(); + m_isolateState.put(isolateId, isolateState); + } else + isolateState = m_isolateState.get(isolateId); + return isolateState; + } + + public DManager() { + m_parms = new HashMap<String, String>(); + + m_isolates = new HashMap<Integer, DIsolate>(); + m_isolates.put(Isolate.DEFAULT_ID, DEFAULT_ISOLATE); + m_event = new LinkedList<DebugEvent>(); + m_sourceLocator = null; + m_squelchEnabled = false; + m_lastConstantPool = null; + m_playerVersion = -1; // -1 => unknown + m_isolateState = new HashMap<Integer, DManagerIsolateState>(); + m_mainState = new DManagerIsolateState(); + m_isolateState.put(Isolate.DEFAULT_ID, m_mainState); + m_inIsolateLock = new Object(); + m_activeIsolateLock = new Object(); + m_wideLines = false; + } + + public void setWideLines(boolean value) { + m_wideLines = value; + } + + public String getURI() { + return m_uri; + } + + public byte[] getSWF() { + return m_swf; + } + + public byte[] getSWD() { + return m_swd; + } + + public byte[] getActions() { + return m_actions; + } + + /** Returns the Flash Player version number; e.g. 9 for Flash Player 9.0 */ + public int getVersion() { + return m_playerVersion; + } + + public SourceLocator getSourceLocator() { + return m_sourceLocator; + } + + public void setSourceLocator(SourceLocator sl) { + m_sourceLocator = sl; + } + + /** + * If this feature is enabled then we do not attempt to attach child + * variables to parents. + */ + public void enableChildAttach(boolean enable, int isolateId) { + getIsolateState(isolateId).m_attachChildren = enable; + } + + // return/clear the last variable seen from an InGetVariable message + public DVariable lastVariable(int isolateId) { + return getIsolateState(isolateId).m_lastInGetVariable; + } + + public void clearLastVariable(int isolateId) { + getIsolateState(isolateId).m_lastInGetVariable = null; + } + + // return/clear the last variable seen from an InCallFunction message + public DVariable lastFunctionCall(int isolateId) { + return getIsolateState(isolateId).m_lastInCallFunction; + } + + public void clearLastFunctionCall(int isolateId) { + getIsolateState(isolateId).m_lastInCallFunction = null; + } + + // return/clear the last binary op result seen from an InBinaryOp message + public DVariable lastBinaryOp(int isolateId) { + return getIsolateState(isolateId).m_lastInBinaryOp; + } + + public void clearLastBinaryOp(int isolateId) { + getIsolateState(isolateId).m_lastInBinaryOp = null; + } + + /* + * Frees up any information we have kept about + */ + void freeCaches(int isolateId) { + clearFrames(isolateId); + freeValueCache(isolateId); + } + + void freeValueCache(int isolateId) { + DManagerIsolateState state = getIsolateState(isolateId); + state.m_previousValues = state.m_values; + state.m_values = new HashMap<Long, DValue>(); + + int size = getFrameCount(isolateId); + for (int i = 0; i < size; i++) + getFrame(i, isolateId).markStale(); + } + + // continuing our execution + void continuing(int isolateId) { + freeCaches(isolateId); + getIsolateState(isolateId).m_suspendInfo = null; + } + + /** + * Variables + */ + DValue getOrCreateValue(long id, int isolateId) { + DValue v = getValue(id, isolateId); + if (v == null) { + v = new DValue(id, isolateId); + putValue(id, v, isolateId); + } + return v; + } + + public DSwfInfo[] getSwfInfos(int isolateId) { + ArrayList<DSwfInfo> swfInfos = getIsolateState(isolateId).m_swfInfo; + synchronized (swfInfos) { + return swfInfos.toArray(new DSwfInfo[swfInfos.size()]); + } + } + + public DSwfInfo getSwfInfo(int at, int isolateId) { + ArrayList<DSwfInfo> swfInfos = getIsolateState(isolateId).m_swfInfo; + synchronized (swfInfos) { + return swfInfos.get(at); + } + } + + public int getSwfInfoCount(int isolateId) { + ArrayList<DSwfInfo> swfInfos = getIsolateState(isolateId).m_swfInfo; + synchronized (swfInfos) { + return swfInfos.size(); + } + } + + /** + * Obtains a SwfInfo object at the given index or if one doesn't yet exist + * at that location, creates a new empty one there and returns it. + */ + DSwfInfo getOrCreateSwfInfo(int at, int isolateId) { + ArrayList<DSwfInfo> swfInfos = getIsolateState(isolateId).m_swfInfo; + synchronized (swfInfos) { + DSwfInfo i = (at > -1 && at < getSwfInfoCount(isolateId)) ? getSwfInfo( + at, isolateId) : null; + if (i == null) { + // are we above water + at = (at < 0) ? 0 : at; + + // fill all the gaps with null; really shouldn't be any... + while (at >= swfInfos.size()) + swfInfos.add(null); + + i = new DSwfInfo(at, isolateId); + swfInfos.set(at, i); + } + return i; + } + } + + /** + * Get the most recently active swfInfo object. We define active as the most + * recently seen swfInfo + */ + DSwfInfo getActiveSwfInfo(int isolateId) { + int count = getSwfInfoCount(isolateId); + + // pick up the last one seen + DSwfInfo swf = getIsolateState(isolateId).m_lastSwfInfo; + + // still don't have one then get or create the most recent one + // works if count = 0 + if (swf == null) + swf = getOrCreateSwfInfo(count - 1, isolateId); + + if (swf.hasAllSource()) { + // already full so create a new one on the end + swf = getOrCreateSwfInfo(count, isolateId); + } + return swf; + } + + /** + * Walk the list of scripts and add them to our swfInfo object This method + * may be called when min/max are zero and the swd has not yet fully loaded + * in the player or it could be called before we have all the scripts. + */ + void tieScriptsToSwf(DSwfInfo info, int isolateId) { + if (!info.hasAllSource()) { + int min = info.getFirstSourceId(); + int max = info.getLastSourceId(); + // System.out.println("attaching scripts "+min+"-"+max+" to "+info.getUrl()); + for (int i = min; i <= max; i++) { + DModule m = getSource(i, isolateId); + if (m == null) { + // this is ok, it means the scripts are coming... + } else { + info.addSource(i, m); + } + } + } + } + + /** + * Record a new source file. + * + * @param name + * filename in "basepath;package;filename" format + * @param swfIndex + * the index of the SWF with which this source is associated, or + * -1 is we don't know + * @return true if our list of source files was modified, or false if we + * already knew about that particular source file. + */ + private boolean putSource(int swfIndex, int moduleId, int bitmap, + String name, String text, int isolateId) { + // if isolateIndex is not -1, augment swfIndex and moduleId with isolate + // info. + Map<Integer, DModule> source = getIsolateState(isolateId).m_source; + synchronized (source) { + // if we haven't already recorded this script then do so. + if (!source.containsKey(moduleId)) { + DModule s = new DModule(this, moduleId, bitmap, name, text, isolateId); + + // put source in our large pool + source.put(moduleId, s); + + // put the source in the currently active swf + DSwfInfo swf; + if (swfIndex == -1) // caller didn't tell us what swf thi is for + swf = getActiveSwfInfo(isolateId); // ... so guess + else + swf = getOrCreateSwfInfo(swfIndex, isolateId); + + swf.addSource(moduleId, s); + + return true; + } + + return false; + } + } + + /** + * Remove our record of a particular source file. + * + * @param id + * the id of the file to forget about. + * @return true if source file was removed; false if we didn't know about it + * to begin with. + */ + private boolean removeSource(int id, int isolateId) { + Map<Integer, DModule> source = getIsolateState(isolateId).m_source; + synchronized (source) { + try { + source.remove(id); + } catch (Exception e) { + return false; + } + return true; + } + } + + public DModule getSource(int id, int isolateId) { + Map<Integer, DModule> source = getIsolateState(isolateId).m_source; + synchronized (source) { + return source.get(id); + } + } + + // @deprecated + public DModule[] getSources() { + Map<Integer, DModule> source = getIsolateState(Isolate.DEFAULT_ID).m_source; + synchronized (source) { + m_sourceListModified = false; + + /* find out the size of the array */ + int count = source.size(); + DModule[] ar = new DModule[count]; + + count = 0; + for (DModule sf : source.values()) + ar[count++] = sf; + return ar; + } + } + + // @deprecated + boolean sourceListModified() { + Map<Integer, DModule> source = getIsolateState(Isolate.DEFAULT_ID).m_source; + synchronized (source) { + return m_sourceListModified; + } + } + + public DValue getValue(long id, int isolateId) { + return getIsolateState(isolateId).m_values.get(id); + } + + /** + * Returns the previous value object for the given id -- that is, the value + * that that object had the last time the player was suspended. Never + * requests it from the player (because it can't, of course). Returns + * <code>null</code> if we don't have a value for that id. + */ + public DValue getPreviousValue(long id, int isolateId) { + return getIsolateState(isolateId).m_previousValues.get(id); + } + + void putValue(long id, DValue v, int isolateId) { + if (id != Value.UNKNOWN_ID) { + getIsolateState(isolateId).m_values.put(id, v); + } + } + + DValue removeValue(long id, int isolateId) { + return getIsolateState(isolateId).m_values.remove((int)id); + } + + void addVariableMember(long parentId, DVariable child, int isolateId) { + DValue parent = getValue(parentId, isolateId); + addVariableMember(parent, child, isolateId); + } + + void addVariableMember(DValue parent, DVariable child, int isolateId) { + if (getIsolateState(isolateId).m_attachChildren) { + // There are certain situations when the Flash player will send us + // more + // than one variable or getter with the same name. Basically, when a + // subclass implements (or overrides) something that was also + // declared in a + // superclass, then we'll see that variable or getter in both the + // superclass and the subclass. + // + // Here are a few situations where that affects the debugger in + // different + // ways: + // + // 1. When a class implements an interface, the class instance + // actually has + // *two* members for each implemented function: One which is public + // and + // represents the implementation function, and another which is + // internal + // to the interface, and represents the declaration of the function. + // Both of these come in to us. In the UI, the one we want to show + // is + // the public one. They come in in random order (they are stored in + // a + // hash table in the VM), so we don't know which one will come + // first. + // + // 2. When a superclass has a private member "m", and a subclass has + // its own + // private member with the same name "m", we will receive both of + // them. + // (They are scoped by different packages.) In this case, the first + // one + // the player sent us is the one from the subclass, and that is the + // one + // we want to display in the debugger. + // + // The following logic correctly deals with all variations of those + // cases. + if (parent != null) { + DVariable existingChildWithSameName = parent.findMember(child + .getName()); + if (existingChildWithSameName != null) { + int existingScope = existingChildWithSameName.getScope(); + int newScope = child.getScope(); + + if (existingScope == VariableAttribute.NAMESPACE_SCOPE + && newScope == VariableAttribute.PUBLIC_SCOPE) { + // This is the case described above where a class + // implements an interface, + // so that class's definition includes both a + // namespace-scoped declaration + // and a public declaration, in random order; in this + // case, the + // namespace-scoped declaration came first. We want to + // use the public + // declaration. + parent.addMember(child); + } else if (existingScope == VariableAttribute.PUBLIC_SCOPE + && newScope == VariableAttribute.NAMESPACE_SCOPE) { + // One of two things happened here: + // + // 1. This is the case described above where a class + // implements an interface, + // so that class's definition includes both a + // namespace-scoped declaration + // and a public declaration, in random order; in this + // case, the + // public declaration came first. It is tempting to use + // the public + // member in this case, but there is a catch... + // 2. It might be more complicated than that: Perhaps + // there is interface I, + // and class C1 implements I, but class C2 extends C1, + // and overrides + // one of the members of I that was already implemented + // by C1. In this + // case, the public declaration from C2 came first, but + // now we are seeing + // a namespace-scoped declaration in C1. We need to + // record that the + // member is public, but we also need to record that it + // is a member + // of the base class, not just a member of the + // superclass. + // + // The easiest way to deal with both cases is to use the + // child that came from + // the superclass, but to change its scope to public. + child.makePublic(); + parent.addMember(child); + } else if (existingScope != VariableAttribute.PRIVATE_SCOPE + && existingScope == newScope) { + // This is a public, protected, internal, or + // namespace-scoped member which + // was defined in a base class and overridden in a + // subclass. We want to + // use the member from the base class, to that the + // debugger knows where the + // variable was actually defined. + parent.addMember(child); + } else if (existingScope == VariableAttribute.PRIVATE_SCOPE + && existingScope == newScope) { + parent.addInheritedPrivateMember(child); + } + } else { + parent.addMember(child); + } + } + // put child in the registry if it has an id and not already there + DValue childValue = (DValue) child.getValue(); + long childId = childValue.getId(); + if (childId != Value.UNKNOWN_ID) { + DValue existingValue = getValue(childId, isolateId); + if (existingValue != null) { + assert existingValue == childValue; // TODO is this right? + // what about getters? + } else { + putValue(childId, childValue, isolateId); + } + } + } + } + + // TODO is this right? + void addVariableMember(long parentId, DVariable child, long doubleAsId, + int isolateId) { + addVariableMember(parentId, child, isolateId); + + // double book the child under another id + if (getIsolateState(isolateId).m_attachChildren) + putValue(doubleAsId, (DValue) child.getValue(), isolateId); + } + + // @deprecated last pool that was read + public String[] getConstantPool() { + return m_lastConstantPool; + } + + /** + * Breakpoints + */ + public DLocation getBreakpoint(int id, int isolateId) { + ArrayList<DLocation> breakpoints = getIsolateState(isolateId).m_breakpoints; + synchronized (breakpoints) { + DLocation loc = null; + int which = findBreakpoint(id, isolateId); + if (which > -1) + loc = breakpoints.get(which); + return loc; + } + } + + int findBreakpoint(int id, int isolateId) { + ArrayList<DLocation> breakpoints = getIsolateState(isolateId).m_breakpoints; + synchronized (breakpoints) { + int which = -1; + int size = breakpoints.size(); + for (int i = 0; which < 0 && i < size; i++) { + DLocation l = breakpoints.get(i); + if (l.getId() == id) + which = i; + } + return which; + } + } + + DLocation removeBreakpoint(int id, int isolateId) { + ArrayList<DLocation> breakpoints = getIsolateState(isolateId).m_breakpoints; + synchronized (breakpoints) { + DLocation loc = null; + int which = findBreakpoint(id, isolateId); + if (which > -1) { + loc = breakpoints.get(which); + breakpoints.remove(which); + } + + return loc; + } + } + + void addBreakpoint(int id, DLocation l, int isolateId) { + ArrayList<DLocation> breakpoints = getIsolateState(isolateId).m_breakpoints; + synchronized (breakpoints) { + breakpoints.add(l); + } + } + + public DLocation[] getBreakpoints(int isolateId) { + ArrayList<DLocation> breakpoints = getIsolateState(isolateId).m_breakpoints; + synchronized (breakpoints) { + return breakpoints.toArray(new DLocation[breakpoints.size()]); + } + } + + /** + * Watchpoints + */ + public DWatch getWatchpoint(int at, int isolateId) { + DManagerIsolateState state = getIsolateState(isolateId); + synchronized (state.m_watchpoints) { + return state.m_watchpoints.get(at); + } + } + + public int getWatchpointCount(int isolateId) { + DManagerIsolateState state = getIsolateState(isolateId); + synchronized (state.m_watchpoints) { + return state.m_watchpoints.size(); + } + } + + public DWatch[] getWatchpoints(int isolateId) { + DManagerIsolateState state = getIsolateState(isolateId); + synchronized (state.m_watchpoints) { + return state.m_watchpoints.toArray(new DWatch[state.m_watchpoints.size()]); + } + } + + boolean addWatchpoint(DWatch w, int isolateId) { + ArrayList<DWatch> lockObject = getIsolateState(isolateId).m_watchpoints; + synchronized (lockObject) { + return lockObject.add(w); + } + } + + DWatch removeWatchpoint(int tag, int isolateId) { + ArrayList<DWatch> lockObject = getIsolateState(isolateId).m_watchpoints; + synchronized (lockObject) { + DWatch w = null; + int at = findWatchpoint(tag, isolateId); + if (at > -1) + w = lockObject.remove(at); + return w; + } + } + + int findWatchpoint(int tag, int isolateId) { + ArrayList<DWatch> lockObject = getIsolateState(isolateId).m_watchpoints; + synchronized (lockObject) { + int at = -1; + int size = getWatchpointCount(isolateId); + for (int i = 0; i < size && at < 0; i++) { + DWatch w = getWatchpoint(i, isolateId); + if (w.getTag() == tag) + at = i; + } + return at; + } + } + + /** + * Isolates + */ + public DIsolate getIsolate(int at) { + + if (at == Isolate.DEFAULT_ID) + return (DIsolate) DEFAULT_ISOLATE; + + synchronized (m_isolates) { + return m_isolates.get(at); + } + } + + public DIsolate getOrCreateIsolate(int at) { + synchronized (m_isolates) { + if (m_isolates.containsKey(at)) { + return m_isolates.get(at); + } else { + DIsolate isolate = new DIsolate(at); + m_isolates.put(at, isolate); + return isolate; + } + } + } + + public int getIsolateCount() { + synchronized (m_isolates) { + return m_isolates.size(); + } + } + + public DIsolate[] getIsolates() { + synchronized (m_isolates) { + return m_isolates.values().toArray(new DIsolate[m_isolates.size()]); + } + } + + boolean addIsolate(DIsolate t) { + synchronized (m_isolates) { + m_isolates.put(t.getId(), t); + return true; + } + } + + void clearIsolates() { + synchronized (m_isolates) { + m_isolates.clear(); + } + } + + DIsolate removeIsolate(int id) { + synchronized (m_isolates) { + DIsolate t = null; + int at = findIsolate(id); + if (at > -1) + t = m_isolates.remove(at); + return t; + } + } + + int findIsolate(int id) { + synchronized (m_isolates) { + if (m_isolates.containsKey(id)) + return id; + else + return -1; + } + } + + void setActiveIsolate(Isolate t) { + synchronized (m_activeIsolateLock) { + if (t == null) { + m_activeIsolate = DEFAULT_ISOLATE; + } else + m_activeIsolate = t; + } + } + + Isolate getActiveIsolate() { + synchronized (m_activeIsolateLock) { + return m_activeIsolate; + } + } + + + void setInIsolate(Isolate t) { + synchronized (m_inIsolateLock) { + if (t == null) { + m_inIsolate = DEFAULT_ISOLATE; + } else + m_inIsolate = t; + } + } + + Isolate getInIsolate() { + synchronized (m_inIsolateLock) { + return m_inIsolate; + } + } + + Isolate getDefaultIsolate() { + return DEFAULT_ISOLATE; + } + + + /** + * Frame stack management related stuff + * + * @return true if we added this frame; false if we ignored it + */ + boolean addFrame(DStackContext ds, int isolateId) { + getIsolateState(isolateId).m_frames.add(ds); + return true; + } + + void clearFrames(int isolateId) { + if (getIsolateState(isolateId).m_frames.size() > 0) + getIsolateState(isolateId).m_previousFrames = getIsolateState(isolateId).m_frames; + getIsolateState(isolateId).m_frames = new ArrayList<DStackContext>(); + } + + public DStackContext getFrame(int at, int isolateId) { + return getIsolateState(isolateId).m_frames.get(at); + } + + public int getFrameCount(int isolateId) { + return getIsolateState(isolateId).m_frames.size(); + } + + public DStackContext[] getFrames(int isolateId) { + ArrayList<DStackContext> frames = getIsolateState(isolateId).m_frames; + return frames.toArray(new DStackContext[frames.size()]); + } + + private boolean stringsEqual(String s1, String s2) { + if (s1 == null) + return s2 == null; + else + return s1.equals(s2); + } + + /** + * Correlates the old list of stack frames, from the last time the player + * was suspended, with the new list of stack frames, attempting to guess + * which frames correspond to each other. This is done so that + * Variable.hasValueChanged() can work correctly for local variables. + */ + private void mapOldFramesToNew(int isolateId) { + ArrayList<DStackContext> previousFrames = null; + ArrayList<DStackContext> frames = null; + Map<Long, DValue> previousValues = null; + + previousFrames = getIsolateState(isolateId).m_previousFrames; + frames = getIsolateState(isolateId).m_frames; + previousValues = getIsolateState(isolateId).m_previousValues; + + int oldSize = previousFrames.size(); + int newSize = frames.size(); + + // discard all old frames (we will restore some of them below) + DValue[] oldFrames = new DValue[oldSize]; + for (int depth = 0; depth < oldSize; depth++) { + oldFrames[depth] = (DValue) previousValues.remove(Value.BASE_ID + - depth); + } + + // Start at the end of the stack (the stack frame farthest from the + // current one), and try to match up stack frames + int oldDepth = oldSize - 1; + int newDepth = newSize - 1; + while (oldDepth >= 0 && newDepth >= 0) { + DStackContext oldFrame = previousFrames.get(oldDepth); + DStackContext newFrame = frames.get(newDepth); + if (oldFrame != null && newFrame != null) { + if (stringsEqual(oldFrame.getCallSignature(), + newFrame.getCallSignature())) { + DValue frame = oldFrames[oldDepth]; + if (frame != null) + previousValues.put(Value.BASE_ID - newDepth, frame); + } + } + oldDepth--; + newDepth--; + } + } + + /** + * Get function is only supported in players that recognize the squelch + * message. + */ + public boolean isGetSupported() { + return m_squelchEnabled; + } + + + /** + * Returns a suspend information on why the Player has suspended execution. + * + * @return see SuspendReason + */ + public DSuspendInfo getSuspendInfo(int isolateId) { + if (m_isolateState.containsKey(isolateId)) { + return m_isolateState.get(isolateId).m_suspendInfo; + } + return null; + } + + public ArrayList<SwfInfo> getIsolateSwfList() { + ArrayList<SwfInfo> result = new ArrayList<SwfInfo>(); + + for (DManagerIsolateState state : m_isolateState.values()) { + if (state.m_swfInfo != null) { + result.addAll(state.m_swfInfo); + } + } + + return result; + } + + /** + * Event management related stuff + */ + public int getEventCount() { + synchronized (m_event) { + return m_event.size(); + } + } + + /** + * Get an object on which callers can call wait(), in order to wait until + * something happens. + * + * Note: The object will be signalled when EITHER of the following happens: + * (1) An event is added to the event queue; (2) The network connection is + * broken (and thus there will be no more events). + * + * @return an object on which the caller can call wait() + */ + public Object getEventNotifier() { + return m_event; + } + + public DebugEvent nextEvent() { + DebugEvent s = null; + synchronized (m_event) { + if (m_event.size() > 0) + s = m_event.removeFirst(); + } + return s; + } + + public synchronized void addEvent(DebugEvent e) { + synchronized (m_event) { + m_event.add(e); + m_event.notifyAll(); // wake up listeners (see getEventNotifier()) + } + } + + /** + * Issued when the socket connection to the player is cut + */ + public void disconnected() { + synchronized (m_event) { + m_event.notifyAll(); // see getEventNotifier() + } + } + + /** + * This is the core routine for decoding incoming messages and deciding what + * should be done with them. We have registered ourself with DProtocol to be + * notified when any incoming messages have been received. + * + * It is important to note that we should not rely on the contents of the + * message since it may be reused after we exit this method. + */ + public void messageArrived(DMessage msg, DProtocol which) { + /* + * at this point we just open up a big switch statement and walk through + * all possible cases + */ + int type = msg.getType(); + // System.out.println("manager msg = "+DMessage.inTypeName(type)); + int inIsolateId = getInIsolate() != null ? getInIsolate().getId() + : Isolate.DEFAULT_ID; + if (inIsolateId != Isolate.DEFAULT_ID) { + msg.setTargetIsolate(inIsolateId); + } + switch (type) { + case DMessage.InVersion: { + long ver = msg.getDWord(); + m_playerVersion = (int) ver; + + // Newer players will send another byte, which is the pointer size + // that is used by the player (in bytes). + int pointerSize; + if (msg.getRemaining() >= 1) + pointerSize = msg.getByte(); + else + pointerSize = 4; + DMessage.setSizeofPtr(pointerSize); + break; + } + + case DMessage.InErrorExecLimit: { + handleFaultEvent(new RecursionLimitFault(msg.getTargetIsolate())); + break; + } + + case DMessage.InErrorWith: { + handleFaultEvent(new InvalidWithFault(msg.getTargetIsolate())); + break; + } + + case DMessage.InErrorProtoLimit: { + handleFaultEvent(new ProtoLimitFault(msg.getTargetIsolate())); + break; + } + + case DMessage.InErrorURLOpen: { +// String url = msg.getString(); +// handleFaultEvent(new InvalidURLFault(url, msg.getTargetIsolate())); + break; + } + + case DMessage.InErrorTarget: { +// String name = msg.getString(); +// handleFaultEvent(new InvalidTargetFault(name, msg.getTargetIsolate())); + break; + } + + case DMessage.InErrorException: { + long offset = msg.getDWord(); + // As of FP9, the player will also send the "toString()" message + // of the exception. But for backward compatibility with older + // players, we won't assume that that is there. + String exceptionMessage; + boolean willExceptionBeCaught = false; + Value thrown = null; + if (msg.getRemaining() > 0) { + exceptionMessage = msg.getString(); + if (msg.getRemaining() > 0) { + if (msg.getByte() != 0) { + willExceptionBeCaught = (msg.getByte() != 0 ? true + : false); + msg.getPtr(); + DVariable thrownVar = extractVariable(msg); + thrown = thrownVar.getValue(); + } + } + } else { + exceptionMessage = ""; //$NON-NLS-1$ + } + ExceptionFault exceptionFault = new ExceptionFault( + exceptionMessage, willExceptionBeCaught, thrown, msg.getTargetIsolate()); + exceptionFault.isolateId = msg.getTargetIsolate(); + handleFaultEvent(exceptionFault); + break; + } + + case DMessage.InErrorStackUnderflow: { +// long offset = msg.getDWord(); + handleFaultEvent(new StackUnderFlowFault(msg.getTargetIsolate())); + break; + } + + case DMessage.InErrorZeroDivide: { +// long offset = msg.getDWord(); + handleFaultEvent(new DivideByZeroFault(msg.getTargetIsolate())); + break; + } + + case DMessage.InErrorScriptStuck: { + handleFaultEvent(new ScriptTimeoutFault(msg.getTargetIsolate())); + break; + } + + case DMessage.InErrorConsole: { + String s = msg.getString(); + handleFaultEvent(new ConsoleErrorFault(s, msg.getTargetIsolate())); + break; + } + + case DMessage.InTrace: { + String text = msg.getString(); + addEvent(new TraceEvent(text)); + break; + } + + case DMessage.InSquelch: { + long state = msg.getDWord(); + m_squelchEnabled = (state != 0) ? true : false; + break; + } + + case DMessage.InParam: { + String name = msg.getString(); + String value = msg.getString(); + + // here's where we get movie = URL and password which I'm not sure + // what to do with? + // System.out.println(name+"="+value); + m_parms.put(name, value); + + // if string is a "movie", then this is a URL + if (name.startsWith("movie")) //$NON-NLS-1$ + m_uri = convertToURI(value); + break; + } + + case DMessage.InPlaceObject: { + long objId = msg.getPtr(); + String path = msg.getString(); + // m_bag.placeObject((int)objId, path); + break; + } + + case DMessage.InSetProperty: { + long objId = msg.getPtr(); + int item = msg.getWord(); + String value = msg.getString(); + break; + } + + case DMessage.InNewObject: { + long objId = msg.getPtr(); + break; + } + + case DMessage.InRemoveObject: { + long objId = msg.getPtr(); + // m_bag.removeObject((int)objId); + break; + } + + case DMessage.InSetVariable: { + long objId = msg.getPtr(); + String name = msg.getString(); + int dType = msg.getWord(); + int flags = (int) msg.getDWord(); + String value = msg.getString(); + + // m_bag.createVariable((int)objId, name, dType, flags, value); + break; + } + + case DMessage.InDeleteVariable: { + long objId = msg.getPtr(); + String name = msg.getString(); + // m_bag.deleteVariable((int)objId, name); + break; + } + + case DMessage.InScript: { + int module = (int) msg.getDWord(); + int bitmap = (int) msg.getDWord(); + String name = msg.getString(); // in "basepath;package;filename" + // format + String text = msg.getString(); + int swfIndex = -1; + int isolateIndex = -1; + + /* new in flash player 9: player tells us what swf this is for */ + if (msg.getRemaining() >= 4) + swfIndex = (int) msg.getDWord(); + + isolateIndex = msg.getTargetIsolate(); + getOrCreateIsolate(isolateIndex); + if (putSource(swfIndex, module, bitmap, name, text, + isolateIndex)) { + // have we changed the list since last query + if (!m_sourceListModified) + addEvent(new FileListModifiedEvent()); + + m_sourceListModified = true; + } + break; + } + + case DMessage.InRemoveScript: { + long module = msg.getDWord(); + int isolateId = msg.getTargetIsolate(); + Map<Integer, DModule> source = getIsolateState(isolateId).m_source; + synchronized (source) { + if (removeSource((int) module, isolateId)) { + // have we changed the list since last query + if (!m_sourceListModified) + addEvent(new FileListModifiedEvent()); + + m_sourceListModified = true; /* current source list is stale */ + } + } + break; + } + + case DMessage.InAskBreakpoints: { + // the player has just loaded a swf and we know the player + // has halted, waiting for us to continue. The only caveat + // is that it looks like it still does a number of things in + // the background which take a few seconds to complete. + int targetIsolate = msg.getTargetIsolate(); + DSuspendInfo iSusInfo = getIsolateState(targetIsolate).m_suspendInfo; + if (iSusInfo == null) { + iSusInfo = new DSuspendInfo(SuspendReason.ScriptLoaded, 0, + 0, 0, 0); + } + break; + } + + case DMessage.InBreakAt: { + long bp = 0, wideLine = 0, wideModule = 0; + if (!m_wideLines) { + bp = msg.getDWord(); + } + else { + wideModule = msg.getDWord(); + wideLine = msg.getDWord(); + } + long id = msg.getPtr(); + String stack = msg.getString(); + int targetIsolate = msg.getTargetIsolate(); + + int module = DLocation.decodeFile(bp); + int line = DLocation.decodeLine(bp); + if (m_wideLines) { + module = (int)wideModule; + line = (int)wideLine; + } + addEvent(new BreakEvent(module, line, targetIsolate)); + break; + } + + case DMessage.InContinue: { + /* we are running again so trash all our variable contents */ + continuing(msg.getTargetIsolate()); + break; + } + + case DMessage.InSetLocalVariables: { +// long objId = msg.getPtr(); + // m_bag.markObjectLocal((int)objId, true); + break; + } + + case DMessage.InSetBreakpoint: { + long count = msg.getDWord(); + int targetIsolate = msg.getTargetIsolate(); + while (count-- > 0) { + long bp = 0, moduleNumber = 0, lineNumber = 0; + if (!m_wideLines) { + bp = msg.getDWord(); + } + else { + moduleNumber = msg.getDWord(); + lineNumber = msg.getDWord(); + } + + int fileId = DLocation.decodeFile(bp); + int line = DLocation.decodeLine(bp); + if (m_wideLines) { + fileId = (int)moduleNumber; + line = (int)lineNumber; + } + + DModule file = null; + file = getSource(fileId, targetIsolate); + + DLocation l = new DLocation(file, line, targetIsolate); + + if (file != null) { + addBreakpoint((int) bp, l, targetIsolate); + } + } + break; + } + + case DMessage.InNumScript: { + /* lets us know how many scripts there are */ + int num = (int) msg.getDWord(); + int targetIsolate = msg.getTargetIsolate(); + DSwfInfo swf; + + /* + * New as of flash player 9: another dword indicating which swf this + * is for. That means we don't have to guess whether this is for an + * old SWF which has just had some more modules loaded, or for a new + * SWF! + */ + if (msg.getRemaining() >= 4) { + int swfIndex = (int) msg.getDWord(); + swf = getOrCreateSwfInfo(swfIndex, targetIsolate); + getIsolateState(targetIsolate).m_lastSwfInfo = swf; + } else { + /* + * This is not flash player 9 (or it is an early build of fp9). + * + * We use this message as a trigger that a new swf has been + * loaded, so make sure we are ready to accept the scripts. + */ + swf = getActiveSwfInfo(targetIsolate); + } + + // It is NOT an error for the player to have sent us a new, + // different sourceExpectedCount from whatever we had before! + // In fact, this happens all the time, whenever a SWF has more + // than one ABC. + swf.setSourceExpectedCount(num); + break; + } + + case DMessage.InRemoveBreakpoint: { + long count = msg.getDWord(); + int isolateId = msg.getTargetIsolate(); + while (count-- > 0) { + long bp = msg.getDWord(); + removeBreakpoint((int) bp, isolateId); + } + break; + + } + + case DMessage.InBreakAtExt: { + long bp = 0, wideLine = 0, wideModule = 0; + if (!m_wideLines) { + bp = msg.getDWord(); + } + else { + wideModule = msg.getDWord(); + wideLine = msg.getDWord(); + } + long num = msg.getDWord(); + + int targetIsolate = msg.getTargetIsolate(); + // System.out.println(msg.getInTypeName()+",bp="+(bp&0xffff)+":"+(bp>>16)); + /* we have stack info to store away */ + clearFrames(targetIsolate); // just in case + int depth = 0; + + while (num-- > 0) { + long bpi = 0, wideLinei= 0, wideModulei = 0; + if (!m_wideLines) { + bpi = msg.getDWord(); + } + else { + wideModulei = msg.getDWord(); + wideLinei = msg.getDWord(); + } + long id = msg.getPtr(); + String stack = msg.getString(); + int module = DLocation.decodeFile(bpi); + int line = DLocation.decodeLine(bpi); + if (m_wideLines) { + module = (int)wideModulei; + line = (int)wideLinei; + } + DModule m = null; + m = getSource(module, targetIsolate); + DStackContext c = new DStackContext(module, line, m, id, stack, + depth, targetIsolate); + // If addFrame() returns false, that means it chose to ignore + // this + // frame, so we do NOT want to increment our depth for the next + // time through the loop. If it returns true, then we do want + // to. + if (addFrame(c, targetIsolate)) + ++depth; + // System.out.println(" this="+id+",@"+(bpi&0xffff)+":"+(bpi>>16)+",stack="+stack); + } + mapOldFramesToNew(targetIsolate); + if (targetIsolate != Isolate.DEFAULT_ID) { + // ask for isolate id if it is present + appendIsolateInfoToFrame(targetIsolate); + + } + break; + + } + + case DMessage.InFrame: { + // For InFrame the first element is really our frame id + DValue frame = null; + DVariable child = null; + ArrayList<DVariable> v = new ArrayList<DVariable>(); + ArrayList<DVariable> registers = new ArrayList<DVariable>(); + int targetIsolate = msg.getTargetIsolate(); + int depth = (int) msg.getDWord(); // depth of frame + + // make sure we have a valid depth + if (depth > -1) { + // first thing is number of registers + int num = (int) msg.getDWord(); + for (int i = 0; i < num; i++) + registers.add(extractRegister(msg, i + 1)); + } + + int currentArg = -1; + boolean gettingScopeChain = false; + + // then our frame itself + while (msg.getRemaining() > 0) { + long frameId = msg.getPtr(); + + if (frame == null) { + frame = getOrCreateValue(frameId, targetIsolate); + extractVariable(msg); // put the rest of the info in the + // trash + } else { + child = extractVariable(msg); + if (currentArg == -1 + && child.getName().equals(ARGUMENTS_MARKER)) { + currentArg = 0; + gettingScopeChain = false; + } else if (child.getName().equals(SCOPE_CHAIN_MARKER)) { + currentArg = -1; + gettingScopeChain = true; + } else if (currentArg >= 0) { + // work around a compiler bug: If the variable's name is + // "undefined", + // then change its name to "_argN", where "N" is the + // argument index, + // e.g. _arg1, _arg2, etc. + ++currentArg; + if (child.getName().equals("undefined")) //$NON-NLS-1$ + child.setName("_arg" + currentArg); //$NON-NLS-1$ + } + + // All args and locals get added as "children" of + // the frame; but scope chain entries do not. + if (!gettingScopeChain) + addVariableMember(frameId, child, targetIsolate); + + // Everything gets added to the ordered list of + // variables that came in. + v.add(child); + } + } + + // let's transfer our newly gained knowledge into the stack context + if (depth == 0) + populateRootNode(frame, v, targetIsolate); + else + populateFrame(depth, v, targetIsolate); + + break; + } + + case DMessage.InOption: { + String s = msg.getString(); + String v = msg.getString(); + m_options.put(s, v); + break; + } + + case DMessage.InGetVariable: { + // For InGetVariable the first element is the original entity we + // requested + DValue parent = null; + DVariable child = null; + String definingClass = null; + int level = 0; + int targetIsolate = msg.getTargetIsolate(); + int highestLevelWithMembers = -1; + List<String> classes = new ArrayList<String>(); + + while (msg.getRemaining() > 0) { + long parentId = msg.getPtr(); + + // build or get parent node + if (parent == null) { + String name = msg.getString(); + + // pull the contents of the node which normally are disposed + // of except if we did a 0,name call + getIsolateState(targetIsolate).m_lastInGetVariable = extractVariable(msg, name); + + parent = getOrCreateValue(parentId, targetIsolate); + } else { + // extract the child and add it to the parent. + child = extractVariable(msg); + if (showMember(child)) { + if (child.isAttributeSet(VariableAttribute.IS_DYNAMIC)) { + // Dynamic attributes always come in marked as a + // member of + // class "Object"; but to the user, it makes more + // sense to + // consider them as members of the topmost class. + if (classes.size() > 0) { + child.setDefiningClass(0, classes.get(0)); + highestLevelWithMembers = Math.max( + highestLevelWithMembers, 0); + } + } else { + child.setDefiningClass(level, definingClass); + if (definingClass != null) { + highestLevelWithMembers = Math.max( + highestLevelWithMembers, level); + } + } + addVariableMember(parent.getId(), child, targetIsolate); + } else { + if (isTraits(child)) { + definingClass = child.getQualifiedName(); + level = classes.size(); + + // If the traits name end with "$", then it + // represents a class object -- + // in other words, the variables inside it are + // static variables of that + // class. In that case, we need to juggle the + // information. For example, + // if we are told that a variable is a member of + // "MyClass$", we actually + // store it into the information for "MyClass". + if (definingClass.endsWith("$")) { //$NON-NLS-1$ + String classWithoutDollar = definingClass + .substring(0, + definingClass.length() - 1); + int indexOfClass = classes + .indexOf(classWithoutDollar); + if (indexOfClass != -1) { + level = indexOfClass; + definingClass = classWithoutDollar; + } + } + + // It wasn't static -- so, add this class to the end + // of the list of classes + if (level == classes.size()) { + classes.add(definingClass); + } + } + } + } + } + + if (parent != null && parent.getClassHierarchy(true) == null) { + parent.setClassHierarchy( + classes.toArray(new String[classes.size()]), + highestLevelWithMembers + 1); + } + + break; + } + + case DMessage.InWatch: // for AS2; sends 16-bit ID field + case DMessage.InWatch2: // for AS3; sends 32-bit ID field + { + // This message is sent whenever a watchpoint is added + // modified or removed. + // + // For an addition, flags will be non-zero and + // success will be true. + // + // For a modification flags will be non-zero. + // and oldFlags will be non-zero and success + // will be true. Additionally oldFlags will not + // be equal to flags. + // + // For a removal flags will be zero. oldFlags + // will be non-zero. + // + // flags identifies the type of watchpoint added, + // see WatchKind. + // + // success indicates whether the operation was successful + // + // request. It will be associated with the watchpoint. + int success = msg.getWord(); + int oldFlags = msg.getWord(); + int oldTag = msg.getWord(); + int flags = msg.getWord(); + int tag = msg.getWord(); + // for AS2, the ID came in above as a Word. For AS3, the above value + // is + // bogus, and it has been sent again as a DWord. + long id = ((type == DMessage.InWatch2) ? msg.getPtr() : msg + .getWord()); + String name = msg.getString(); + int targetIsolate = msg.getTargetIsolate(); + + if (success != 0) { + if (flags == 0) { + removeWatchpoint(oldTag, targetIsolate); + } else { + // modification or addition is the same to us + // a new watch is created and added into the table + // while any old entry if it exists is removed. + removeWatchpoint(oldTag, targetIsolate); + DWatch w = new DWatch(id, name, flags, tag, targetIsolate); + addWatchpoint(w, targetIsolate); + } + } + break; + } + + case DMessage.InGetSwf: { + // we only house the swf temporarily, PlayerSession then + // pieces it back into swfinfo record. Also, we don't + // send any extra data in the message so that we need not + // copy the bytes. + m_swf = msg.getData(); + break; + } + + case DMessage.InGetSwd: { + // we only house the swd temporarily, PlayerSession then + // pieces it back into swfinfo record. + m_swd = msg.getData(); + break; + } + + case DMessage.InBreakReason: { + // the id map 1-1 with out SuspendReason interface constants + int suspendReason = msg.getWord(); + int suspendPlayer = msg.getWord(); // item index of player + int breakOffset = (int) msg.getDWord(); // current script offset + int prevBreakOffset = (int) msg.getDWord(); // prev script offset + int nextBreakOffset = (int) msg.getDWord(); // next script offset + int targetIsolate = msg.getTargetIsolate(); + + getIsolateState(targetIsolate).m_suspendInfo = new DSuspendInfo( + suspendReason, suspendPlayer, breakOffset, + prevBreakOffset, nextBreakOffset); + + // augment the current frame with this information. It + // should work ok since we only get this message after a + // InBreakAtExt message + try { + DStackContext c = getFrame(0, targetIsolate); + c.setOffset(breakOffset); + c.setSwfIndex(suspendPlayer); + } catch (Exception e) { + if (Trace.error) { + Trace.trace("Oh my god, gag me with a spoon...getFrame(0) call failed"); //$NON-NLS-1$ + e.printStackTrace(); + } + } + break; + } + + // obtain raw action script byte codes + case DMessage.InGetActions: { + int item = msg.getWord(); + int rsvd = msg.getWord(); + int at = (int) msg.getDWord(); + int len = (int) msg.getDWord(); + int i = 0; + + m_actions = (len <= 0) ? null : new byte[len]; + while (len-- > 0) + m_actions[i++] = (byte) msg.getByte(); + + break; + } + + // obtain data about a SWF + case DMessage.InSwfInfo: { + int count = msg.getWord(); + int targetIsolate = msg.getTargetIsolate(); + for (int i = 0; i < count; i++) { + long index = msg.getDWord(); + long id = msg.getPtr(); + + // get it + DSwfInfo info = null; + + info = getOrCreateSwfInfo((int) index, targetIsolate); + getIsolateState(targetIsolate).m_lastSwfInfo = info; + + // remember which was last seen + + if (id != 0) { + boolean debugComing = (msg.getByte() == 0) ? false : true; + byte vmVersion = (byte) msg.getByte(); // AS vm version + // number (1 = avm+, + // 0 == avm-) + int rsvd1 = msg.getWord(); + + long swfSize = msg.getDWord(); + long swdSize = msg.getDWord(); + long scriptCount = msg.getDWord(); + long offsetCount = msg.getDWord(); + long breakpointCount = msg.getDWord(); + + long port = msg.getDWord(); + String path = msg.getString(); + String url = msg.getString(); + String host = msg.getString(); + Map<Long, Integer> local2global = new HashMap<Long, Integer>(); + int minId = Integer.MAX_VALUE; + int maxId = Integer.MIN_VALUE; + // now we read in the swd debugging map (which provides + // local to global mappings of the script ids + /* anirudhs: Parsing this is only necessary if we are in + AVM1. (See PlayerSession::run(), there is a vmVersion + check before calling parseSwfSwd(). */ + if (swdSize > 0) { + long num = msg.getDWord(); + for (int j = 0; j < num; j++) { + if (msg.getRemaining() < DMessage.getSizeofPtr()) { + /* The SWD debugging map sent out by + * AVM2 often runs short usually in 64-bit + * debug player. We can stop with what we know + * and move on. + */ + break; + } + long local = msg.getPtr(); + int global = (int) msg.getDWord(); + local2global.put(local, global); + minId = (global < minId) ? global : minId; + maxId = (global > maxId) ? global : maxId; + } + } + // If its a new record then the swf size would have been + // unknown at creation time + boolean justCreated = (info.getSwfSize() == 0); + + // if we are a avm+ engine then we don't wait for the swd to + // load + if (vmVersion > 0) { + debugComing = false; + info.setVmVersion(vmVersion); + info.setPopulated(); // added by mmorearty on 9/5/05 for + // RSL debugging + } + + // update this swfinfo with the lastest data + info.freshen(id, path, url, host, port, debugComing, + swfSize, swdSize, breakpointCount, offsetCount, + scriptCount, local2global, minId, maxId); + // now tie any scripts that have been loaded into this + // swfinfo object + tieScriptsToSwf(info, targetIsolate); + + // notify if its newly created + if (justCreated) + addEvent(new SwfLoadedEvent(id, (int) index, path, url, + host, port, swfSize)); + } else { + // note our state before marking it + boolean alreadyUnloaded = info.isUnloaded(); + + // clear it out + info.setUnloaded(); + + // notify if this information is new. + if (!alreadyUnloaded) + addEvent(new SwfUnloadedEvent(info.getId(), + info.getPath(), (int) index)); + } + // System.out.println("[SWFLOAD] Loaded "+path+", size="+swfSize+", scripts="+scriptCount); + } + break; + } + + // obtain the constant pool of some player + case DMessage.InConstantPool: { + int item = msg.getWord(); + int count = (int) msg.getDWord(); + + String[] pool = new String[count]; + for (int i = 0; i < count; i++) { + long id = msg.getPtr(); + DVariable var = extractVariable(msg); + + // we only need the contents of the variable + pool[i] = var.getValue().getValueAsString(); + } + m_lastConstantPool = pool; + break; + } + + // obtain one or more function name line number mappings. + case DMessage.InGetFncNames: { + long id = msg.getDWord(); // module id + long count = msg.getDWord(); // number of entries + + // get the DModule + DModule m = getSource((int) id, msg.getTargetIsolate()); + if (m != null) { + for (int i = 0; i < count; i++) { + int offset = (int) msg.getDWord(); + int firstLine = (int) msg.getDWord(); + int lastLine = (int) msg.getDWord(); + String name = msg.getString(); + + // now add the entries + m.addLineFunctionInfo(offset, firstLine, lastLine, name); + } + } + break; + } + + case DMessage.InCallFunction: + case DMessage.InBinaryOp: { + // For InCallFunction the first element is the original function we + // requested + DValue parent = null; + int targetIsolate = msg.getTargetIsolate(); + DVariable child = null; + String definingClass = null; + int level = 0; + int highestLevelWithMembers = -1; + List<String> classes = new ArrayList<String>(); + + if (type == DMessage.InBinaryOp) + msg.getDWord(); // id + + while (msg.getRemaining() > 0) { + long parentId = msg.getPtr(); + + // build or get parent node + if (parent == null) { + String name = msg.getString(); + + // pull the contents of the node which normally are disposed + // of except if we did a 0,name call + DVariable var = extractVariable(msg, name); + if (type == DMessage.InCallFunction) { + getIsolateState(targetIsolate).m_lastInCallFunction = var; + } + else { + getIsolateState(targetIsolate).m_lastInBinaryOp = var; + } + + parent = getOrCreateValue(parentId, targetIsolate); + } else { + // extract the child and add it to the parent. + child = extractVariable(msg); + if (showMember(child)) { + if (child.isAttributeSet(VariableAttribute.IS_DYNAMIC)) { + // Dynamic attributes always come in marked as a + // member of + // class "Object"; but to the user, it makes more + // sense to + // consider them as members of the topmost class. + if (classes.size() > 0) { + child.setDefiningClass(0, classes.get(0)); + highestLevelWithMembers = Math.max( + highestLevelWithMembers, 0); + } + } else { + child.setDefiningClass(level, definingClass); + if (definingClass != null) { + highestLevelWithMembers = Math.max( + highestLevelWithMembers, level); + } + } + addVariableMember(parent.getId(), child, targetIsolate); + } else { + if (isTraits(child)) { + definingClass = child.getQualifiedName(); + level = classes.size(); + + // If the traits name end with "$", then it + // represents a class object -- + // in other words, the variables inside it are + // static variables of that + // class. In that case, we need to juggle the + // information. For example, + // if we are told that a variable is a member of + // "MyClass$", we actually + // store it into the information for "MyClass". + if (definingClass.endsWith("$")) { //$NON-NLS-1$ + String classWithoutDollar = definingClass + .substring(0, + definingClass.length() - 1); + int indexOfClass = classes + .indexOf(classWithoutDollar); + if (indexOfClass != -1) { + level = indexOfClass; + definingClass = classWithoutDollar; + } + } + + // It wasn't static -- so, add this class to the end + // of the list of classes + if (level == classes.size()) { + classes.add(definingClass); + } + } + } + } + } + + if (parent != null && parent.getClassHierarchy(true) == null) { + parent.setClassHierarchy( + classes.toArray(new String[classes.size()]), + highestLevelWithMembers + 1); + } + + break; + } + + case DMessage.InIsolateCreate: { + long id = msg.getDWord(); + isolateCreate((int) id); + + break; + } + + case DMessage.InIsolateExit: { + long id = msg.getDWord(); + // Implementation dependency on runtime in case id mechanism is + // changed: + // Typecast id into an int. + DIsolate isolate = removeIsolate((int) id); + addEvent(new IsolateExitEvent(isolate)); + + break; + } + + case DMessage.InIsolateEnumerate: { + // clearIsolates(); + // + // long lenIsolate = msg.getDWord(); + // + // for ( int i = 0; i < lenIsolate; i++) { + // long id = msg.getDWord(); + // addIsolate(new DIsolate(id)); + // } + + break; + } + + case DMessage.InSetActiveIsolate: { + long id = msg.getDWord(); + + boolean success = msg.getByte() != 0 ? true : false; + + /** Ignore inset since we don't wait + * for response anymore. + */ +// synchronized (m_activeIsolateLock) { +// if (success) { +// int at = findIsolate((int) id); +// if (at > -1) +// setActiveIsolate(getIsolate(at)); +// } else { +// setActiveIsolate(null); +// } +// } + + break; + } + + case DMessage.InIsolate: { + long id = msg.getDWord(); + synchronized (m_inIsolateLock) { + int at = findIsolate((int) id); + if (at != -1) + setInIsolate(getIsolate(at)); + else { + if (id != Isolate.DEFAULT_ID) { + setInIsolate(isolateCreate((int) id)); + } else + setInIsolate(null); + } + } + break; + } + + case DMessage.InSetExceptionBreakpoint: { + + int result = msg.getWord(); + String exceptionBP = msg.getString(); + int remaining = msg.getRemaining(); + break; + } + + case DMessage.InRemoveExceptionBreakpoint: { + int result = msg.getWord(); + String exceptionBP = msg.getString(); + int remaining = msg.getRemaining(); + break; + } + + default: { + break; + } + } + } + + private DIsolate isolateCreate(int id) { + int idx = findIsolate(id); + if (idx == -1) { + DIsolate isolate = new DIsolate(id); + addIsolate(isolate); + setInIsolate(isolate); + addEvent(new IsolateCreateEvent(isolate)); + return isolate; + } + return getIsolate(idx); + + } + + private void appendIsolateInfoToFrame(int isolateid) { + // augment the current frame with this information. It + // should work ok since we only get this message after a + // InBreakAtExt message + try { + DStackContext c = getFrame(0, isolateid); + c.setIsolateId(isolateid); + } catch (Exception e) { + if (Trace.error) { + Trace.trace("Oh my god, gag me with a spoon...getFrame(0) call failed"); //$NON-NLS-1$ + e.printStackTrace(); + } + } + } + + /** + * Returns whether a given child member should be shown, or should be + * filtered out. + */ + private boolean showMember(DVariable child) { + if (isTraits(child)) + return false; + return true; + } + + /** + * Returns whether this is not a variable at all, but is instead a + * representation of a "traits" object. A "traits" object is the Flash + * player's way of describing one class. + */ + private boolean isTraits(DVariable variable) { + Value value = variable.getValue(); + if (value.getType() == VariableType.UNKNOWN + && Value.TRAITS_TYPE_NAME.equals(value.getTypeName())) { + return true; + } + return false; + } + + /** + * Here's where some ugly stuff happens. Since our context contains more + * info than what's contained within the stackcontext, we augment it with + * the variables. Also, we build up a list of variables that appears under + * root, that can be accessed without further qualification; this includes + * args, locals and _global. + */ + void populateRootNode(DValue frame, ArrayList<DVariable> orderedChildList, + int isolateId) { + // first populate the stack node with children + populateFrame(0, orderedChildList, isolateId); + + /** + * We mark it as members obtained so that we don't go to the player and + * request it, which would be bad, since its our artifical creation. + */ + DValue base = getOrCreateValue(Value.BASE_ID, isolateId); + base.setMembersObtained(true); + + /** + * Technically, we don't need to create the following nodes, but we like + * to give them nice type names + */ + + // now let's create a _global node and attach it to base + } + + /** + * We are done, so let's look for a number of special variables, since our + * frame comes in 3 pieces. First off is a "this" pointer, followed by a + * "$arguments" dummy node, followed by a "super" which marks the end of the + * arguments. + * + * All of this stuff gets pulled apart after we build the frame node. + */ + void populateFrame(int depth, ArrayList<DVariable> frameVars, int isolateId) { + // get our stack context + DStackContext context = null; + boolean inArgs = false; + int nArgs = -1; + boolean inScopeChain = false; + + // create a root node for each stack frame; first is at BASE_ID + DValue root = getOrCreateValue(Value.BASE_ID - depth, isolateId); + + if (depth < getFrameCount(isolateId)) + context = getFrame(depth, isolateId); + + // trim all current args from this context + if (context != null) + context.removeAllVariables(); + + // use the ordered child list + Iterator<DVariable> e = frameVars.iterator(); + while (e.hasNext()) { + DVariable v = e.next(); + String name = v.getName(); + + // let's clear a couple of attributes that may get in our way + v.clearAttribute(VariableAttribute.IS_LOCAL); + v.clearAttribute(VariableAttribute.IS_ARGUMENT); + if (name.equals("this")) //$NON-NLS-1$ + { + if (context != null) + context.setThis(v); + + // from our current frame, put a pseudo this entry into the + // cache and hang it off base, mark it as an implied arg + v.setAttribute(VariableAttribute.IS_ARGUMENT); + addVariableMember(root, v, isolateId); + + // also add this variable under THIS_ID + if (depth == 0) + putValue(Value.THIS_ID, (DValue) v.getValue(), isolateId); + } else if (name.equals("super")) //$NON-NLS-1$ + { + // we are at the end of the arg list and let's make super part + // of global + inArgs = false; + } else if (name.equals(ARGUMENTS_MARKER)) { + inArgs = true; + + // see if we can extract an arg count from this variable + try { + nArgs = ((Number) (v.getValue().getValueAsObject())) + .intValue(); + } catch (NumberFormatException nfe) { + } + } else if (name.equals(SCOPE_CHAIN_MARKER)) { + inArgs = false; + inScopeChain = true; + } else { + // add it to our root, marking it as an arg if we know, + // otherwise local + if (inArgs) { + v.setAttribute(VariableAttribute.IS_ARGUMENT); + + if (context != null) + context.addArgument(v); + + // decrement arg count if we have it + if (nArgs > -1) { + if (--nArgs <= 0) + inArgs = false; + } + } else if (inScopeChain) { + if (context != null) + context.addScopeChainEntry(v); + } else { + v.setAttribute(VariableAttribute.IS_LOCAL); + if (context != null) + context.addLocal(v); + } + + // add locals and arguments to root + if (!inScopeChain) + addVariableMember(root, v, isolateId); + } + } + } + + /** + * Map DMessage / Player attributes to VariableAttributes + */ + int toAttributes(int pAttr) { + int attr = pAttr; /* 1-1 mapping */ + return attr; + } + + DVariable extractVariable(DMessage msg) { + DVariable v = extractVariable(msg, msg.getString()); + return v; + } + + /** + * Build a variable based on the information we can extract from the + * messsage + */ + DVariable extractVariable(DMessage msg, String name) { + int oType = msg.getWord(); + int flags = (int) msg.getDWord(); + return extractAtom(msg, name, oType, flags); + } + + /** + * Extracts an builds a register variable + */ + DVariable extractRegister(DMessage msg, int number) { + int oType = msg.getWord(); + return extractAtom(msg, "$" + number, oType, 0); //$NON-NLS-1$ + } + + /** + * Does the job of pulling together a variable based on the type of object + * encountered. + */ + DVariable extractAtom(DMessage msg, String name, int oType, int flags) { + int vType = VariableType.UNKNOWN; + Object value = null; + String typeName = ""; //$NON-NLS-1$ + String className = ""; //$NON-NLS-1$ + boolean isPrimitive = false; + + /* now we vary depending upon type */ + switch (oType) { + case DMessage.kNumberType: { + String s = msg.getString(); + double dval = Double.NaN; + try { + dval = Double.parseDouble(s); + } catch (NumberFormatException nfe) { + } + + value = new Double(dval); + isPrimitive = true; + break; + } + + case DMessage.kBooleanType: { + int bval = msg.getByte(); + value = new Boolean((bval == 0) ? false : true); + isPrimitive = true; + break; + } + + case DMessage.kStringType: { + String s = msg.getString(); + + value = s; + isPrimitive = true; + break; + } + + case DMessage.kObjectType: + case DMessage.kNamespaceType: { + long oid = msg.getPtr(); + long cType = (oid == -1) ? 0 : msg.getDWord(); + int isFnc = (oid == -1) ? 0 : msg.getWord(); + int rsvd = (oid == -1) ? 0 : msg.getWord(); + typeName = (oid == -1) ? "" : msg.getString(); //$NON-NLS-1$ + /* anirudhs: Date fix for expression evaluation */ + /* Player 10.2 onwards, the typename for Date comes + * as <dateformat>@oid where example of date format is: + * <Tue Feb 7 15:41:16 GMT+0530 2012> + * We have to fix the typename to how it originally + * appeared prior to this bug which is Date@oid. + * Note that even player 9 did not send oType as 11, + * instead oType was Object where as typeName was Date. + * What the customer sees is expression evaluation will + * always try to interpret date as a number. (ECMA.defaultValue + * has a check for preferredType of Date to be String) + */ + if (typeName.startsWith("<")) { //$NON-NLS-1$ + int atIndex = typeName.indexOf('@'); + String dateVal = typeName; + if (atIndex > -1) { + dateVal = typeName.substring(0, atIndex); + } + SimpleDateFormat dFormat = new SimpleDateFormat("<EEE MMM d HH:mm:ss 'GMT'z yyyy>"); //$NON-NLS-1$ + try { + Date dateObj = dFormat.parse(dateVal); + if (dateObj != null && dateObj.getTime() != 0) { + oType = DMessage.kDateType; + typeName = "Date" + typeName.substring(atIndex); //$NON-NLS-1$ + } + } + catch (ParseException e) { + //ignore + } + } + + className = DVariable.classNameFor(cType, false); + value = new Long(oid); + vType = (isFnc == 0) ? VariableType.OBJECT : VariableType.FUNCTION; + break; + } + + case DMessage.kMovieClipType: { + long oid = msg.getPtr(); + long cType = (oid == -1) ? 0 : msg.getDWord(); + long rsvd = (oid == -1) ? 0 : msg.getDWord(); + typeName = (oid == -1) ? "" : msg.getString(); //$NON-NLS-1$ + className = DVariable.classNameFor(cType, true); + + value = new Long(oid); + vType = VariableType.MOVIECLIP; + break; + } + + case DMessage.kNullType: { + value = null; + isPrimitive = true; + break; + } + + case DMessage.kUndefinedType: { + value = Value.UNDEFINED; + isPrimitive = true; + break; + } + + case DMessage.kTraitsType: { + // This one is special: When passed to the debugger, it indicates + // that the "variable" is not a variable at all, but rather is a + // class name. For example, if class Y extends class X, then + // we will send a kDTypeTraits for class Y; then we'll send all the + // members of class Y; then we'll send a kDTypeTraits for class X; + // and then we'll send all the members of class X. This is only + // used by the AVM+ debugger. + vType = VariableType.UNKNOWN; + typeName = Value.TRAITS_TYPE_NAME; + break; + } + + case DMessage.kReferenceType: + case DMessage.kArrayType: + case DMessage.kObjectEndType: + case DMessage.kStrictArrayType: + case DMessage.kDateType: + case DMessage.kLongStringType: + case DMessage.kUnsupportedType: + case DMessage.kRecordSetType: + case DMessage.kXMLType: + case DMessage.kTypedObjectType: + case DMessage.kAvmPlusObjectType: + default: { + // System.out.println("<unknown>"); + break; + } + } + int isolateId = msg.getTargetIsolate(); + // create the variable based on the content we received. + DValue valueObject = null; + + // If value is a Long, then it is the ID of a non-primitive object; + // look up to see if we already have that object in our cache. If + // it is already in our cache, then we just want to modify the + // existing object with the new values. + if (value instanceof Long) { + valueObject = getValue(((Long) value).longValue(), isolateId); + } + + if (valueObject == null) { + // we didn't find it in the cache, so make a new Value + + if (isPrimitive) { + valueObject = DValue.forPrimitive(value, isolateId); + valueObject.setAttributes(toAttributes(flags)); + } else { + valueObject = new DValue(vType, typeName, className, + toAttributes(flags), value, isolateId); + } + + if (value instanceof Long + && (toAttributes(flags) & VariableAttribute.HAS_GETTER) == 0) + putValue(((Long) value).longValue(), valueObject, isolateId); + } else { + // we found it in the cache, so just modify the properties + // of the old Value + + if (isPrimitive) { + // figure out some of the properties + DValue temp = DValue.forPrimitive(value, isolateId); + vType = temp.getType(); + typeName = temp.getTypeName(); + className = temp.getClassName(); + } + + valueObject.setType(vType); + valueObject.setTypeName(typeName); + valueObject.setClassName(className); + valueObject.setAttributes(toAttributes(flags)); + valueObject.setValue(value); + } + if (valueObject != null) { + valueObject.setIsolateId(isolateId); + } + DVariable var = new DVariable(name, valueObject, isolateId); + return var; + } + + /** + * The player sends us a URI using '|' instead of ':' + */ + public static String convertToURI(String playerURL) { + int index = playerURL.indexOf('|'); + StringBuilder sb = new StringBuilder(playerURL); + while (index > 0) { + sb.setCharAt(index, ':'); + index = playerURL.indexOf('|', index + 1); + } + return sb.toString(); + } + + /** + * Tell us that we are about to start executing user code in the player, + * such as a getter, a setter, or a function call. If a FaultEvent comes in + * while the code is executing, it is not added to the event queue in the + * normal way -- instead, it is saved, and is returned when + * endPlayerCodeExecution() is called. + */ + public void beginPlayerCodeExecution(int isolateId) { + DManagerIsolateState state = getIsolateState(isolateId); + state.m_executingPlayerCode = true; + state.m_faultEventDuringPlayerCodeExecution = null; + } + + /** + * Informs us that user code is no longer executing, and returns the fault, + * if any, which occurred while the code was executing. + */ + public FaultEvent endPlayerCodeExecution(int isolateId) { + DManagerIsolateState state = getIsolateState(isolateId); + state.m_executingPlayerCode = false; + FaultEvent e = state.m_faultEventDuringPlayerCodeExecution; + state.m_faultEventDuringPlayerCodeExecution = null; + return e; + } + + /** + * When we've just received any FaultEvent from the player, this function + * gets called. If a getter/setter is currently executing, we'll save the + * fault for someone to get later by calling endGetterSetter(). Otherwise, + * normal code execution is taking place, so we'll add the event to the + * event queue. + */ + private void handleFaultEvent(FaultEvent faultEvent) { + DManagerIsolateState isolateState = getIsolateState(faultEvent.isolateId); + boolean executingPlayerCode = isolateState.m_executingPlayerCode; + if (executingPlayerCode) { + FaultEvent faultEventDuringPlayerCodeExecution = isolateState.m_faultEventDuringPlayerCodeExecution; + + if (faultEventDuringPlayerCodeExecution == null) // only save the + // first fault + { + // save the event away so that when someone later calls + // endGetterSetter(), we can return the fault that + // occurred + isolateState.m_faultEventDuringPlayerCodeExecution = faultEvent; + } + } else { + // regular code is running; so post the event to the + // event queue which the client debugger will see + addEvent(faultEvent); + } + } + + /* + * (non-Javadoc) + * + * @see flash.tools.debugger.SourceLocator#locateSource(java.lang.String, + * java.lang.String, java.lang.String) + */ + public InputStream locateSource(String path, String pkg, String name) { + if (m_sourceLocator != null) + return m_sourceLocator.locateSource(path, pkg, name); + + return null; + } + + /* + * (non-Javadoc) + * + * @see flash.tools.debugger.SourceLocator#getChangeCount() + */ + public int getChangeCount() { + if (m_sourceLocator != null) + return m_sourceLocator.getChangeCount(); + + return 0; + } + + /** + * Returns the value of a Flash Player option that was requested by + * OutGetOption and returned by InOption. + * + * @param optionName + * the name of the option + * @return its value, or null + */ + public String getOption(String optionName) { + return m_options.get(optionName); + } +}