blob: a8b1c8a7b165116c3b468e5da896fa84b89d5e78 [file] [log] [blame]
/*
* 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);
}
}