/* * 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.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.Socket; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import flash.tools.debugger.AIRLaunchInfo; import flash.tools.debugger.Frame; import flash.tools.debugger.IDebuggerCallbacks; import flash.tools.debugger.ILauncher; import flash.tools.debugger.InProgressException; import flash.tools.debugger.Isolate; import flash.tools.debugger.IsolateController; import flash.tools.debugger.IsolateSession; import flash.tools.debugger.Location; import flash.tools.debugger.NoResponseException; import flash.tools.debugger.NotConnectedException; import flash.tools.debugger.NotSupportedException; import flash.tools.debugger.NotSuspendedException; import flash.tools.debugger.PlayerDebugException; import flash.tools.debugger.Session; import flash.tools.debugger.SessionManager; import flash.tools.debugger.SourceFile; import flash.tools.debugger.SourceLocator; import flash.tools.debugger.SuspendedException; import flash.tools.debugger.SwfInfo; import flash.tools.debugger.Value; import flash.tools.debugger.ValueAttribute; import flash.tools.debugger.Variable; import flash.tools.debugger.VariableAttribute; import flash.tools.debugger.VariableType; import flash.tools.debugger.VersionException; import flash.tools.debugger.Watch; import flash.tools.debugger.concrete.DProtocol.ListenerIndex; import flash.tools.debugger.events.DebugEvent; import flash.tools.debugger.events.ExceptionFault; import flash.tools.debugger.events.FaultEvent; import flash.tools.debugger.expression.ECMA; import flash.tools.debugger.expression.PlayerFaultException; import flash.util.Trace; public class PlayerSession implements Session, DProtocolNotifierIF, Runnable, IsolateController { public static final int MAX_STACK_DEPTH = 256; public static final long MAX_TERMINATE_WAIT_MILLIS = 10000; private Socket m_socket; private DProtocol m_protocol; private DManager m_manager; private IDebuggerCallbacks m_debuggerCallbacks; private Process m_process; private Map<String, Object> m_prefs; // WARNING -- accessed from multiple threads private static final String s_newline = System.getProperty("line.separator"); //$NON-NLS-1$ private volatile boolean m_isConnected; // WARNING -- accessed from multiple threads private volatile boolean m_isHalted; // WARNING -- accessed from multiple threads private volatile boolean m_incoming; // WARNING -- accessed from multiple threads private volatile boolean m_lastResponse; // whether there was a reponse from the last message to the Player private volatile HashMap<Integer, PlayerSessionIsolateStatus> m_isolateStatus = new HashMap<Integer, PlayerSessionIsolateStatus>(); private int m_watchTransactionTag; private Boolean m_playerCanCallFunctions; private Boolean m_playerSupportsWatchpoints; private Boolean m_playerCanBreakOnAllExceptions; private Boolean m_playerSupportsConcurrency; private Boolean m_playerSupportsWideLine; private ILauncher launcher; /** * The URL that was launched, or <code>null</code> if not known. Note: * This is NOT the value returned by getURI(). getURI() returns the * URL that came from the Player, and is therefore probably the URI of * the SWF; but m_launchedUrl contains the URL that we tried to launch, * which might be an HTML wrapper, e.g. http://localhost/myapp.html */ private String m_launchUrl; private AIRLaunchInfo m_airLaunchInfo; // null if this is not an AIR app static volatile boolean m_debugMsgOn; // debug ONLY; turned on with "set $debug_messages = 1" volatile int m_debugMsgSize; // debug ONLY; controlled with "set $debug_message_size = NNN" static volatile boolean m_debugMsgFileOn; // debug ONLY for file dump; turned on with "set $debug_message_file = 1" volatile int m_debugMsgFileSize; // debug ONLY for file dump; controlled with "set $debug_message_file_size = NNN" //FIXME: Make this concurrency aware /** * A simple cache of previous "is" and "instanceof" queries, in order to * avoid having to send redundant messages to the player. */ private Map<String, Boolean> m_evalIsAndInstanceofCache = new HashMap<String, Boolean>(); private volatile int m_lastPreIsolate = Isolate.DEFAULT_ID; private final Map<Integer, IsolateSession> m_isolateSessions; private static final String DEBUG_MESSAGES = "$debug_messages"; //$NON-NLS-1$ private static final String DEBUG_MESSAGE_SIZE = "$debug_message_size"; //$NON-NLS-1$ private static final String DEBUG_MESSAGE_FILE = "$debug_message_file"; //$NON-NLS-1$ private static final String DEBUG_MESSAGE_FILE_SIZE = "$debug_message_file_size"; //$NON-NLS-1$ private static final String CONSOLE_ERRORS = "$console_errors"; //$NON-NLS-1$ private static final String FLASH_PREFIX = "$flash_"; //$NON-NLS-1$ PlayerSession(Socket s, DProtocol proto, DManager manager, IDebuggerCallbacks debuggerCallbacks) { m_isConnected = false; m_isHalted = false; m_socket = s; m_protocol = proto; m_manager = manager; m_prefs = Collections.synchronizedMap(new HashMap<String, Object>()); m_incoming = false; m_debugMsgOn = false; m_debugMsgSize = 16; m_debugMsgFileOn = false; m_debugMsgFileSize = 128; m_watchTransactionTag = 1; // number that is sent for each watch transaction that occurs m_playerCanCallFunctions = null; m_debuggerCallbacks = debuggerCallbacks; m_isolateSessions = Collections.synchronizedMap(new HashMap<Integer, IsolateSession>()); } private static PlayerSession createFromSocketHelper(Socket s, IDebuggerCallbacks debuggerCallbacks, DProtocol proto) throws IOException { // let the manager hear incoming messages DManager manager = new DManager(); PlayerSession session = new PlayerSession(s, proto, manager, debuggerCallbacks); return session; } /** * @deprecated Use createFromSocketWithOptions * @param s * @param debuggerCallbacks * @return * @throws IOException */ public static PlayerSession createFromSocket(Socket s, IDebuggerCallbacks debuggerCallbacks) throws IOException { DProtocol proto = DProtocol.createFromSocket(s); return createFromSocketHelper(s, debuggerCallbacks, proto); } /** * Creates a session from the socket. Sets session specific * socket settings and stores the callback object. * @param s * @param debuggerCallbacks * @param sessionManager * @return * @throws IOException */ public static PlayerSession createFromSocketWithOptions(Socket s, IDebuggerCallbacks debuggerCallbacks, SessionManager sessionManager) throws IOException { DProtocol proto = DProtocol.createFromSocket(s, sessionManager); return createFromSocketHelper(s, debuggerCallbacks, proto); } /* getter */ public DMessageCounter getMessageCounter() { return m_protocol.getMessageCounter(); } public String getURI() { return m_manager.getURI(); } public boolean playerSupportsGet() { return m_manager.isGetSupported(); } public int playerVersion() { return m_manager.getVersion(); } public SourceLocator getSourceLocator() { return m_manager.getSourceLocator(); } /* * @see flash.tools.debugger.Session#setSourceLocator(flash.tools.debugger.SourceLocator) */ public void setSourceLocator(SourceLocator sourceLocator) { m_manager.setSourceLocator(sourceLocator); } /** * If the manager started the process for us, then note it here. We will attempt to kill * it when we go down */ void setProcess(Process proc) { m_process = proc; } /* * @see flash.tools.debugger.Session#getLaunchProcess() */ public Process getLaunchProcess() { return m_process; } /** * Set preference * If an invalid preference is passed, it will be silently ignored. */ public void setPreferences(Map<String, ? extends Object> map) { m_prefs.putAll(map); mapBack(); } public Set<String> keySet() { return m_prefs.keySet(); } public Object getPreferenceAsObject(String pref) { return m_prefs.get(pref); } /** * Set a property. Special logic for debug message boolean */ public void setPreference(String pref, int value) { m_prefs.put(pref, new Integer(value)); mapBack(); // change in console messages? if (pref.equals(CONSOLE_ERRORS)) sendConsoleErrorsAsTrace(value == 1); // generic message for flash player wherein "$flash_xxx" causes "xxx" to be sent if (pref.startsWith(FLASH_PREFIX)) sendOptionMessage(pref.substring(FLASH_PREFIX.length()), Integer.toString(value)); } // helper for mapBack() private int mapBackOnePreference(String preferenceName, int defaultValue) { Object prefValue = getPreferenceAsObject(preferenceName); if (prefValue != null) return ((Integer)prefValue).intValue(); else return defaultValue; } // helper for mapBack() private boolean mapBackOnePreference(String preferenceName, boolean defaultValue) { Object prefValue = getPreferenceAsObject(preferenceName); if (prefValue != null) return ((Integer)prefValue).intValue() != 0 ? true : false; else return defaultValue; } // look for preferences, that map back to variables private void mapBack() { m_debugMsgOn = mapBackOnePreference(DEBUG_MESSAGES, m_debugMsgOn); m_debugMsgSize = mapBackOnePreference(DEBUG_MESSAGE_SIZE, m_debugMsgSize); m_debugMsgFileOn = mapBackOnePreference(DEBUG_MESSAGE_FILE, m_debugMsgFileOn); m_debugMsgFileSize = mapBackOnePreference(DEBUG_MESSAGE_FILE_SIZE, m_debugMsgFileSize); } public int getPreference(String pref) { int val = 0; Integer i = (Integer)m_prefs.get(pref); if (i == null) throw new NullPointerException(); else val = i.intValue(); return val; } /* * @see flash.tools.debugger.Session#isConnected() */ public boolean isConnected() { return m_isConnected; } /* * @see flash.tools.debugger.Session#isSuspended() */ public boolean isSuspended() throws NotConnectedException { if (!isConnected()) throw new NotConnectedException(); return m_isHalted; } /* * @see flash.tools.debugger.Session#isIsolateSuspended() */ public boolean isWorkerSuspended(int isolateId) throws NotConnectedException { if (isolateId == Isolate.DEFAULT_ID) return isSuspended(); if (!isConnected()) throw new NotConnectedException(); if (m_isolateStatus.containsKey(isolateId)) { return m_isolateStatus.get(isolateId).m_isHalted; } return false; } /** * Start up the session listening for incoming messages on the socket */ public boolean bind() throws VersionException { boolean bound = false; if (m_isConnected) return false; // mark that we are connected m_isConnected = true; // attach us to the pipe (we are last to ensure that DManager and msg counter // get updated first m_protocol.addListener(ListenerIndex.PlayerSession, this); // start up the receiving thread bound = m_protocol.bind(); // transmit our first few adjustment messages sendStopWarning(); sendStopOnFault(); sendEnumerateOverride(); sendFailureNotify(); sendInvokeSetters(); sendSwfloadNotify(); sendGetterTimeout(); sendSetterTimeout(); boolean responded = sendSquelch(true, Isolate.DEFAULT_ID); // now note in our preferences whether get is working or not. setPreference(SessionManager.PLAYER_SUPPORTS_GET, playerSupportsGet() ? 1 : 0); if (supportsConcurrency()) { sendConcurrentDebugger(); } if (supportsWideLineNumbers()) { sendWideLineDebugger(); } // Spawn a background thread which fetches the SWF and SWD // from the Player and uses them to build function name tables // for each source file Thread t = new Thread(this, "SWF/SWD reader"); //$NON-NLS-1$ t.setDaemon(true); t.start(); // we're probably using a bad version if (!responded) throw new VersionException(); return bound; } /** * Permanently stops the debugging session and breaks the * connection to the Player */ public void unbind() { unbind(false); } /** * @param requestTerminate * if true, and if the player to which we are attached is capable * of terminating itself (e.g. Adobe AIR), then the player will * be told to terminate. * @return true if the player is capable of terminating itself and has been * told to do so */ private boolean unbind(boolean requestTerminate) { // If the caller asked us to terminate the player, then we first check // whether the player to which we are connected is capable of that. // (The web-based players are not; Adobe AIR is.) requestTerminate = requestTerminate && playerCanTerminate(); DMessage dm = DMessageCache.alloc(1); dm.setType(DMessage.OutExit); dm.putByte((byte)(requestTerminate ? 1 : 0)); sendMessage(dm); // unbind from the socket, so that we don't receive any more messages m_protocol.unbind(); // kill the socket try { m_socket.close(); } catch(IOException io) {} m_isConnected = false; m_isHalted = false; return requestTerminate; // true if player was told to terminate } /** * Execute the specified AppleScript by passing it to /usr/bin/osascript. * * @param appleScript * the AppleScript to execute, as a series of lines * @param argv * any arguments; these can be accessed from within your * AppleScript via "item 1 or argv", "item 2 of argv", etc. * @return any text which was sent to stdout by /usr/bin/osascript, with the * trailing \n already removed */ private String executeAppleScript(String[] appleScript, String[] argv) { StringBuilder retval = new StringBuilder(); try { List<String> execArgs = new LinkedList<String>(); // "osascript" is the command-line way of executing AppleScript. execArgs.add("/usr/bin/osascript"); //$NON-NLS-1$ execArgs.add("-"); //$NON-NLS-1$ if (argv != null) { for (int i=0; i<argv.length; ++i) execArgs.add(argv[i]); } Process osascript = Runtime.getRuntime().exec(execArgs.toArray(new String[execArgs.size()])); // feed our AppleScript code to osascript's stdin OutputStream outputStream = osascript.getOutputStream(); PrintWriter writer = new PrintWriter(outputStream, true); writer.println("on run argv"); //$NON-NLS-1$ // this gives the name "argv" to the command-line args for (int i=0; i<appleScript.length; ++i) writer.println(appleScript[i]); writer.println("end run"); //$NON-NLS-1$ writer.close(); InputStreamReader reader = new InputStreamReader(osascript.getInputStream()); int ch; while ( (ch=reader.read()) != -1 ) retval.append((char)ch); } catch (IOException e) { // ignore } return retval.toString().replaceAll("\n$", ""); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Execute the specified AppleScript by passing it to /usr/bin/osascript. * * @param appleScriptFilename * The name of the file containing AppleScript to execute. This * must be relative to PlayerSession.java. * @param argv * any arguments; these can be accessed from within your * AppleScript via "item 1 or argv", "item 2 of argv", etc. * @return any text which was sent to stdout by /usr/bin/osascript, with the * trailing \n already removed * @throws IOException */ private String executeAppleScript(String appleScriptFilename, String[] argv) throws IOException { InputStream stm = null; try { stm = PlayerSession.class.getResourceAsStream(appleScriptFilename); BufferedReader reader = new BufferedReader(new InputStreamReader(stm)); String line; List<String> appleScriptLines = new ArrayList<String>(); while ( (line=reader.readLine()) != null ) appleScriptLines.add(line); String[] lines = appleScriptLines.toArray(new String[appleScriptLines.size()]); return executeAppleScript(lines, argv); } finally { if (stm != null) { stm.close(); } } } /** * Checks whether the specified Macintosh web browser is currently * running. You should only call this function if you have already * checked that you are running on a Mac. * * @param browserName a name, e.g. "Safari", "Firefox", "Camino" * @return true if currently running */ private Set<String> runningApplications() { String running = executeAppleScript( new String[] { "tell application \"System Events\"", //$NON-NLS-1$ " name of processes", //$NON-NLS-1$ "end tell" //$NON-NLS-1$ }, null ); String[] apps = running.split(", "); //$NON-NLS-1$ Set<String> retval = new HashSet<String>(); for (int i=0; i<apps.length; ++i) retval.add(apps[i]); return retval; } /** * Destroys all objects related to the connection * including the process that was tied to this * session via SessionManager.launch(), if it * exists. */ public void terminate() { boolean playerWillTerminateItself = false; // unbind first try { // Tell player to end session. Note that this is just a hint, and will often // do nothing. For example, the Flash player running in a browser will // currently never terminate when you tell it to, but the AIR player will // terminate. playerWillTerminateItself = unbind(true); } catch(Exception e) { } if (!playerWillTerminateItself) { if (System.getProperty("os.name").toLowerCase().startsWith("mac os x")) //$NON-NLS-1$ //$NON-NLS-2$ { if (m_airLaunchInfo != null) { // nothing we need to do -- Process.destroy() will kill the AIR app } else if (m_launchUrl != null && m_launchUrl.length() > 0) { boolean closedAnyWindows = false; Set<String> runningApps = runningApplications(); if (runningApps.contains("Safari")) //$NON-NLS-1$ { try { String url = m_launchUrl.replaceAll(" ", "%20"); //$NON-NLS-1$ //$NON-NLS-2$ String safariClosedAnyWindows = executeAppleScript("appleScriptCloseSafariWindow.txt", new String[] { url }); //$NON-NLS-1$ if ("true".equals(safariClosedAnyWindows)) { //$NON-NLS-1$ closedAnyWindows = true; } else if ( "appquit".equals(safariClosedAnyWindows) ) { //$NON-NLS-1$ closedAnyWindows = true; //we closed Safari, verify safari was closed runningApps = waitForMacAppQuit("Safari"); //$NON-NLS-1$ } } catch (IOException e) { // ignore } } if (!closedAnyWindows && runningApps.contains("Camino")) //$NON-NLS-1$ { // For local file: URLs, Camino uses "file://localhost/..." instead of "file:///..." String url = m_launchUrl.replaceFirst("^file:///", "file://localhost/"); //$NON-NLS-1$ //$NON-NLS-2$ try { String caminoClosedAnyWindows = executeAppleScript("appleScriptCloseCaminoWindow.txt", new String[] { url }); //$NON-NLS-1$ if ("true".equals(caminoClosedAnyWindows)) { //$NON-NLS-1$ closedAnyWindows = true; } else if ( "appquit".equals(caminoClosedAnyWindows) ) { //$NON-NLS-1$ closedAnyWindows = true; //we closed camino, verify camino was closed runningApps = waitForMacAppQuit("Camino"); //$NON-NLS-1$ } } catch (IOException e) { // ignore } } // The standalone player on the Mac has gone through several name changes, // so we have to look for all of these. String[] macStandalonePlayerNames = { "Flash Player Debugger", // New name as of Player 10.1 //$NON-NLS-1$ "Flash Player", // New name as of 12/4/06 //$NON-NLS-1$ "SAFlashPlayer", // An older name //$NON-NLS-1$ "standalone" // Another older name //$NON-NLS-1$ }; for (int i=0; !closedAnyWindows && i<macStandalonePlayerNames.length; ++i) { if (runningApps.contains(macStandalonePlayerNames[i])) { executeAppleScript(new String[] { "tell application \"" + macStandalonePlayerNames[i] + "\" to quit" }, null); //$NON-NLS-1$ //$NON-NLS-2$ waitForMacAppQuit(macStandalonePlayerNames[i]); closedAnyWindows = true; } } } } // if we have a process pop it if (m_process != null) { try { //if a launcher is set for handling the launcher operations then use it. if(null != launcher) { m_debuggerCallbacks.terminateDebugTarget(m_process,launcher); } else { m_debuggerCallbacks.terminateDebugTarget(m_process); } } catch (IOException e) { // ignore } } } else if (m_process != null) { try { m_process.waitFor(); } catch (Exception e) { } } // now clear it all m_isConnected = false; m_isHalted = false; } /** * Utility function to wait for a mac application to quit. * This waits for a maximum of MAX_TERMINATE_WAIT_MILLIS. * * Waiting is important because applescript "quit" is not * synchronous and launching a URL while the browser is * quitting is not good. (See FB-21879) * @return Set<String> of running applications. */ private Set<String> waitForMacAppQuit(String browser) { Set<String> runningApps; boolean appClosed = true; final long startMillis = System.currentTimeMillis(); final long waitMillis = 100; do { runningApps = runningApplications(); if ( runningApps.contains(browser) ) { appClosed = false; try { Thread.sleep(waitMillis); } catch (InterruptedException e) { return runningApps; } long currentMillis = System.currentTimeMillis(); if ( currentMillis - startMillis >= MAX_TERMINATE_WAIT_MILLIS ) break; } else { appClosed = true; } } while ( !appClosed ); return runningApps; } /* * @see flash.tools.debugger.Session#resume() */ public void resume() throws NotSuspendedException, NotConnectedException, NoResponseException { resumeWorker(Isolate.DEFAULT_ID); } /* * @see flash.tools.debugger.Session#suspend() */ public void suspend() throws SuspendedException, NotConnectedException, NoResponseException { suspendWorker(Isolate.DEFAULT_ID); } /** * Obtain all the suspend information */ public DSuspendInfo getSuspendInfo() { return getSuspendInfoIsolate(Isolate.DEFAULT_ID); } /** * Return the reason that the player has suspended itself. */ public int suspendReason() { DSuspendInfo info = getSuspendInfo(); return info.getReason(); } /** * Return the offset in which the player has suspended itself. The BreakReason * message contains both reason and offset. */ public int getSuspendOffset() { DSuspendInfo info = getSuspendInfo(); return info.getOffset(); } /** * Return the offset in which the player has suspended itself. The BreakReason * message contains both reason and offset. */ public int getSuspendActionIndex() { DSuspendInfo info = getSuspendInfo(); return info.getActionIndex(); } /** * Obtain information about the various SWF(s) that have been * loaded into the Player, for this session. * * Note: As SWFs are loaded by the Player a SwfLoadedEvent is * fired. At this point, a call to getSwfInfo() will provide * updated information. * * @return array of records describing the SWFs */ public SwfInfo[] getSwfs() throws NoResponseException { return getSwfsWorker(Isolate.DEFAULT_ID); } /** * Request information on a particular swf, used by DSwfInfo * to fill itself correctly */ public void requestSwfInfo(int at, int isolateId) throws NoResponseException { // nope don't have it...might as well go out and ask for all of them. DMessage dm = DMessageCache.alloc(4); dm.setType( DMessage.OutSwfInfo ); dm.setTargetIsolate(isolateId); dm.putWord(at); dm.putWord(0); // rserved int to = getPreference(SessionManager.PREF_CONTEXT_RESPONSE_TIMEOUT); if (!simpleRequestResponseMessage(dm, DMessage.InSwfInfo, to)) throw new NoResponseException(to); } /** * Request a set of actions from the player */ public byte[] getActions(int which, int at, int len) throws NoResponseException { byte[] actions = null; // send a actions message DMessage dm = DMessageCache.alloc(12); dm.setType( DMessage.OutGetActions ); dm.putWord(which); dm.putWord(0); // rsrvd dm.putDWord(at); dm.putDWord(len); // request action bytes int to = getPreference(SessionManager.PREF_CONTEXT_RESPONSE_TIMEOUT); if (simpleRequestResponseMessage(dm, DMessage.InGetActions, to)) actions = m_manager.getActions(); else throw new NoResponseException(to); return actions; } /* * @see flash.tools.debugger.Session#stepInto() */ public void stepInto() throws NotSuspendedException, NoResponseException, NotConnectedException { stepIntoWorker(Isolate.DEFAULT_ID); } /* * @see flash.tools.debugger.Session#stepOut() */ public void stepOut() throws NotSuspendedException, NoResponseException, NotConnectedException { stepOutWorker(Isolate.DEFAULT_ID); } /* * @see flash.tools.debugger.Session#stepOver() */ public void stepOver() throws NotSuspendedException, NoResponseException, NotConnectedException { stepOverWorker(Isolate.DEFAULT_ID); } /* * @see flash.tools.debugger.Session#stepContinue() */ public void stepContinue() throws NotSuspendedException, NoResponseException, NotConnectedException { if (!isSuspended()) throw new NotSuspendedException(); // send a step-continue message and then wait for the Flash player to tell us that is has // resumed execution if (!simpleRequestResponseMessage(DMessage.OutStepContinue, DMessage.InContinue)) throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); } /** * Sends a request to the player to obtain function names. * The resultant message end up populating the function name array * for the given DModule. * * @param moduleId * @param lineNbr * @return */ public void requestFunctionNames(int moduleId, int lineNbr, int isolateId) throws VersionException, NoResponseException { // only player 9 supports this message if (m_manager.getVersion() >= 9) { DMessage dm = DMessageCache.alloc(8); dm.setType(DMessage.OutGetFncNames); dm.setTargetIsolate(isolateId); dm.putDWord(moduleId); dm.putDWord(lineNbr); if (!simpleRequestResponseMessage(dm, DMessage.InGetFncNames)) throw new NoResponseException(0); } else { throw new VersionException(); } } /** * From a given file identifier return a source file object */ public SourceFile getFile(int fileId, int isolateId) { return m_manager.getSource(fileId, isolateId); } /** * Get a list of breakpoints */ public Location[] getBreakpointList() { return m_manager.getBreakpoints(Isolate.DEFAULT_ID); } /* * @see flash.tools.debugger.Session#setBreakpoint(int, int) */ public Location setBreakpoint(int fileId, int lineNum) throws NoResponseException, NotConnectedException { return setBreakpointWorker(fileId, lineNum, Isolate.DEFAULT_ID); } /* * @see flash.tools.debugger.Session#clearBreakpoint(flash.tools.debugger.Location) */ public Location clearBreakpoint(Location local) { /* first find it */ SourceFile source = local.getFile(); int fileId = source.getId(); int lineNum = local.getLine(); int bp = DLocation.encodeId(fileId, lineNum); int isolateId = local.getIsolateId(); Location l = null; l = m_manager.getBreakpoint(bp, isolateId); if (l != null) { /* send the message */ int wideLineSize = 0; if (supportsWideLineNumbers()) wideLineSize = 4; DMessage dm = DMessageCache.alloc(8 + wideLineSize); dm.setType(DMessage.OutRemoveBreakpoints); dm.setTargetIsolate(isolateId); dm.putDWord(1); if (!supportsWideLineNumbers()) dm.putDWord(bp); else { dm.putDWord(fileId); dm.putDWord(lineNum); } sendMessage(dm); /* no callback from the player so we remove it ourselves */ m_manager.removeBreakpoint(bp, isolateId); } return l; } /* * @see flash.tools.debugger.Session#getWatchList() */ public Watch[] getWatchList() throws NoResponseException, NotConnectedException { return getWatchListWorker(Isolate.DEFAULT_ID); } /* * @see flash.tools.debugger.Session#getWatchList() */ public Watch[] getWatchListWorker(int isolateId) throws NoResponseException, NotConnectedException { return m_manager.getWatchpoints(isolateId); } private Watch setWatch(long varId, String memberName, int kind, int isolateId) throws NoResponseException, NotConnectedException, NotSupportedException { // we really have two cases here, one where we add a completely new // watchpoint and the other where we modify an existing one. // In either case the DManager logic is such that the last watchpoint // in the list will contain our id if successful. Watch w = null; int tag = m_watchTransactionTag++; if (addWatch(varId, memberName, kind, tag, isolateId)) { // good that we got a response now let's check that // it actually worked. int count = m_manager.getWatchpointCount(isolateId); if (count > 0) { DWatch lastWatch = m_manager.getWatchpoint(count-1, isolateId); if (lastWatch.getTag() == tag) w = lastWatch; } } return w; } /* * @see flash.tools.debugger.Session#setWatch(flash.tools.debugger.Variable, java.lang.String, int) */ public Watch setWatch(Value v, String memberName, int kind) throws NoResponseException, NotConnectedException, NotSupportedException { return setWatch(v.getId(), memberName, kind, v.getIsolateId()); } public Watch setWatch(Watch watch) throws NoResponseException, NotConnectedException, NotSupportedException { return setWatch(watch.getValueId(), watch.getMemberName(), watch.getKind(), watch.getIsolateId()); } /* * @see flash.tools.debugger.Session#clearWatch(flash.tools.debugger.Watch) */ public Watch clearWatch(Watch watch) throws NoResponseException, NotConnectedException { Watch[] list = getWatchListWorker(watch.getIsolateId()); Watch w = null; if ( removeWatch(watch.getValueId(), watch.getMemberName(), watch.getIsolateId()) ) { // now let's first check the size of the list, it // should now be one less if (m_manager.getWatchpointCount(watch.getIsolateId()) < list.length) { // ok we made a change. So let's compare list and see which // one went away Watch[] newList = getWatchListWorker(watch.getIsolateId()); for(int i=0; i<newList.length; i++) { // where they differ is the missing one if (list[i] != newList[i]) { w = list[i]; break; } } // might be the last one... if (w == null) w = list[list.length-1]; } } return w; } /* * @see flash.tools.debugger.Session#getVariableList() */ public Variable[] getVariableList() throws NotSuspendedException, NoResponseException, NotConnectedException, VersionException { return getVariableListWorker(Isolate.DEFAULT_ID); } public Variable[] getVariableListWorker(int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException, VersionException { // make sure the player has stopped and send our message awaiting a response if (!isWorkerSuspended(isolateId)) throw new NotSuspendedException(); requestFrame(0, isolateId); // our 0th frame gets our local context // now let's request all of the special variables too getValueWorker(Value.GLOBAL_ID, isolateId); getValueWorker(Value.THIS_ID, isolateId); getValueWorker(Value.ROOT_ID, isolateId); // request as many levels as we can get int i = 0; Value v = null; do { v = getValueWorker(Value.LEVEL_ID-i, isolateId); } while( i++ < 128 && v != null); // now that we've primed the DManager we can request the base variable whose // children are the variables that are available v = m_manager.getValue(Value.BASE_ID, isolateId); if (v == null) throw new VersionException(); return v.getMembers(this); } /* * @see flash.tools.debugger.Session#getFrames() */ public Frame[] getFrames() throws NotConnectedException { return m_manager.getFrames(Isolate.DEFAULT_ID); } /** * Asks the player to return information regarding our current context which includes * this pointer, arguments for current frame, locals, etc. */ public void requestFrame(int depth, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException { if (playerSupportsGet()) { if (!isWorkerSuspended(isolateId)) throw new NotSuspendedException(); int timeout = getPreference(SessionManager.PREF_CONTEXT_RESPONSE_TIMEOUT); DMessage dm = DMessageCache.alloc(4); dm.setType(DMessage.OutGetFrame); dm.setTargetIsolate(isolateId); dm.putDWord(depth); // depth of zero if (!simpleRequestResponseMessage(dm, DMessage.InFrame, timeout)) { throw new NoResponseException(timeout); } pullUpActivationObjectVariables(depth, isolateId); } } /** * The compiler sometimes creates special local variables called * "activation objects." When it decides to do this (e.g. if the * current function contains any anonymous functions, try/catch * blocks, complicated E4X expressions, or "with" clauses), then * all locals and arguments are actually stored as children of * this activation object, rather than the usual way. * * We need to hide this implementation detail from the user. So, * if we find any activation objects among the locals of the current * function, then we will "pull up" its members, and represent them * as if they were actually args/locals of the function itself. * * @param depth the depth of the stackframe we are fixing; 0 is topmost */ private void pullUpActivationObjectVariables(int depth, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException { DValue frame = m_manager.getValue(Value.BASE_ID-depth, isolateId); if (frame == null) return; DStackContext context = m_manager.getFrame(depth, isolateId); DVariable[] frameVars = (DVariable[]) frame.getMembers(this); Map<String, DVariable> varmap = new LinkedHashMap<String, DVariable>(frameVars.length); // preserves order List<DVariable> activationObjects = new ArrayList<DVariable>(); Pattern activationObjectNamePattern = Pattern.compile("^.*\\$\\d+$"); //$NON-NLS-1$ // loop through all frame variables, and separate them into two // groups: activation objects, and all others (locals and arguments) for (int i=0; i<frameVars.length; ++i) { DVariable member = frameVars[i]; Matcher matcher = activationObjectNamePattern.matcher(member.getName()); if (matcher.matches()) activationObjects.add(member); else varmap.put(member.getName(), member); } // If there are no activation objects, then we don't need to do anything if (activationObjects.size() == 0) return; // overwrite existing args and locals with ones pulled from the activation objects for (int i=0; i<activationObjects.size(); ++i) { DVariable activationObject = activationObjects.get(i); DVariable[] activationMembers = (DVariable[]) activationObject.getValue().getMembers(this); for (int j=0; j<activationMembers.length; ++j) { DVariable member = activationMembers[j]; int attributes = member.getAttributes(); // For some odd reason, the activation object often contains a whole bunch of // other variables that we shouldn't be displaying. I don't know what they // are, but I do know that they are all marked "static". if ((attributes & VariableAttribute.IS_STATIC) != 0) continue; // No matter what the activation object member's scope is, we want all locals // and arguments to be considered "public" attributes &= ~(VariableAttribute.PRIVATE_SCOPE | VariableAttribute.PROTECTED_SCOPE | VariableAttribute.NAMESPACE_SCOPE); attributes |= VariableAttribute.PUBLIC_SCOPE; member.setAttributes(attributes); String name = member.getName(); DVariable oldvar = varmap.get(name); int vartype; if (oldvar != null) vartype = oldvar.getAttributes() & (VariableAttribute.IS_ARGUMENT | VariableAttribute.IS_LOCAL); else vartype = VariableAttribute.IS_LOCAL; member.setAttributes(member.getAttributes() | vartype); varmap.put(name, member); } context.convertLocalToActivationObject(activationObject); } for (DVariable var: varmap.values()) { frame.addMember(var); if (var.isAttributeSet(VariableAttribute.IS_LOCAL)) { context.addLocal(var); } else if (var.isAttributeSet(VariableAttribute.IS_ARGUMENT)) { if (var.getName().equals("this")) //$NON-NLS-1$ context.setThis(var); else context.addArgument(var); } } } /* * @see flash.tools.debugger.Session#getValue(int) */ public Value getValue(long valueId) throws NotSuspendedException, NoResponseException, NotConnectedException { return getValueWorker(valueId, Isolate.DEFAULT_ID); } public Value getValueWorker(long valueId, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException { DValue val = null; if (!isWorkerSuspended(isolateId)) throw new NotSuspendedException(); // get it from cache if we can val = m_manager.getValue(valueId, isolateId); if (val == null) { // if a special variable, then we need to trigger a local frame call, otherwise just use id to get it if (valueId < Value.UNKNOWN_ID) { requestFrame(0, isolateId); // force our current frame to get populated, BASE_ID will be available } else if (valueId > Value.UNKNOWN_ID) { requestVariable(valueId, null, isolateId); } // after all this we should have our variable cache'd so try again if it wasn't there the first time val = m_manager.getValue(valueId, isolateId); } return val; } /** * Returns the current value object for the given id; never requests it from the player. */ public Value getRawValue(long valueId, int isolateId) { return m_manager.getValue(valueId, isolateId); } /** * 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 Value getPreviousValue(long valueId, int isolateId) { return m_manager.getPreviousValue(valueId, isolateId); } /** * Launches a request to obtain all the members of the specified variable, and * store them in the variable which would be returned by * {@link DManager#getVariable(long)}. * * @param valueId id of variable whose members we want; underlying Variable must * already be known by the PlayerSessionManager. * * @throws NoResponseException * @throws NotConnectedException * @throws NotSuspendedException */ void obtainMembers(long valueId, int isolateId) throws NoResponseException, NotConnectedException, NotSuspendedException { if (!isWorkerSuspended(isolateId)) throw new NotSuspendedException(); // Get it from cache. Normally, this should never fail; however, in // the case of Flex Builder, which is multithreaded, it is possible // that a thread has called this even after a different thread has // single-stepped, so that the original variable is no longer valid. // So, we'll check for a null return value. DValue v = m_manager.getValue(valueId, isolateId); if (v != null && !v.membersObtained()) { requestVariable(valueId, null, false, true, isolateId); } } public Value getGlobal(String name) throws NotSuspendedException, NoResponseException, NotConnectedException { return getGlobalWorker(name, Isolate.DEFAULT_ID); } public Value getGlobalWorker(String name, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException { Value v = getValue(0, name, isolateId); if (v==null || v.getType() == VariableType.UNDEFINED) return null; else return v; } /** * Get the value of the variable named 'name' using varId * as the context id for the Variable. * * This call is used to fire getters, where the id must * be that of the original object and not the object id * of where the getter actually lives. For example * a getter a() may live under o.__proto__.__proto__ * but you must use the id of o and the name of 'a' * in order for the getter to fire correctly. [Note: This * paragraph was written for AS2; __proto__ doesn't exist * in AS3. TODO: revise this paragraph] */ public Value getValue(long varId, String name, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException { Value v = null; if (isWorkerSuspended(isolateId)) { int fireGetter = getPreference(SessionManager.PREF_INVOKE_GETTERS); // disable children attaching to parent variables and clear our // most recently seen variable m_manager.clearLastVariable(isolateId); m_manager.enableChildAttach(false, isolateId); try { requestVariable(varId, name, (fireGetter != 0), false, isolateId); DVariable lastVariable = m_manager.lastVariable(isolateId); if (lastVariable != null) v = lastVariable.getValue(); else v = DValue.forPrimitive(Value.UNDEFINED, isolateId); } catch (NoResponseException e) { if (fireGetter != 0) { // We fired a getter -- most likely, what happened is that that getter // (which is actual code in the user's movie) just took too long to // calculate its value. So rather than throwing an exception, we store // some error text for the value of the variable itself. // // TODO [mmorearty 4/20/06] Even though I wrote the below code, I now // am wondering if it is incorrect that I am calling addVariableMember(), // because in every other case, this function does not add members to // existing objects. Need to revisit this. v = new DValue(VariableType.STRING, "String", "String", ValueAttribute.IS_EXCEPTION, //$NON-NLS-1$ //$NON-NLS-2$ e.getLocalizedMessage(), isolateId); if (varId != 0) { DVariable var = new DVariable(name, (DValue)v, isolateId); m_manager.enableChildAttach(true, isolateId); m_manager.addVariableMember(varId, var, isolateId); } } else { throw e; // re-throw } } finally { // reset our attach flag, so that children attach to parent variables. m_manager.enableChildAttach(true, isolateId); } } else throw new NotSuspendedException(); return v; } private void requestVariable(long id, String name, int isolateId) throws NoResponseException, NotConnectedException, NotSuspendedException { requestVariable(id, name, false, false, isolateId); } /** * @param thisValue the value of the "this" pointer; meaningless if isConstructor is true * @param isConstructor whether we're calling a constructor as opposed to a regular function * @param funcname the name of the function to call (or class whose constructor we're calling) * @param args the args to the function * @return the return value of the function */ private Value callFunction(Value thisValue, boolean isConstructor, String funcname, Value[] args, int isolateId) throws PlayerDebugException { if (!isWorkerSuspended(isolateId)) throw new NotSuspendedException(); if (!playerCanCallFunctions(isolateId)) throw new NotSupportedException(PlayerSessionManager.getLocalizationManager().getLocalizedTextString("functionCallsNotSupported")); //$NON-NLS-1$ // name = getRawMemberName(id, name); m_manager.clearLastFunctionCall(isolateId); DMessage dm = buildCallFunctionMessage(isConstructor, thisValue, funcname, args); dm.setTargetIsolate(isolateId); // make sure any exception during the setter gets held onto m_manager.beginPlayerCodeExecution(isolateId); // TODO wrong timeout int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT); timeout += 500; // give the player enough time to raise its timeout exception boolean result = simpleRequestResponseMessage(dm, DMessage.InCallFunction, timeout); // tell manager we're done; ignore returned FaultEvent m_manager.endPlayerCodeExecution(isolateId); if (!result) throw new NoResponseException(timeout); DVariable lastFunctionCall = m_manager.lastFunctionCall(isolateId); if (lastFunctionCall != null) return lastFunctionCall.getValue(); else return DValue.forPrimitive(Value.UNDEFINED, isolateId); } /* * @see flash.tools.debugger.Session#callFunction(flash.tools.debugger.Value, java.lang.String, flash.tools.debugger.Value[]) */ public Value callFunction(Value thisValue, String funcname, Value[] args) throws PlayerDebugException { return callFunctionWorker(thisValue, funcname, args, Isolate.DEFAULT_ID); } public Value callFunctionWorker(Value thisValue, String funcname, Value[] args, int isolateId) throws PlayerDebugException { Value retval = callPseudoFunction(thisValue, funcname, args, isolateId); if (retval != null) { return retval; } return callFunction(thisValue, false, funcname, args, isolateId); } /** * Checks to see if the function being called is a debugger pseudofunction such as * $obj(), and if so, handles that directly rather than calling the player. Returns * null if the function being called is not a pseudofunction. */ private Value callPseudoFunction(Value thisValue, String funcname, Value[] args, int isolateId) throws PlayerDebugException{ if (thisValue.getType() == VariableType.UNDEFINED || thisValue.getType() == VariableType.NULL) { if ("$obj".equals(funcname)) { //$NON-NLS-1$ return callObjPseudoFunction(args, isolateId); } } return null; } /** * Handles a call to the debugger pseudofunction $obj() -- e.g. $obj(1234) returns * a pointer to the object with id 1234. */ private Value callObjPseudoFunction(Value[] args, int isolateId) throws PlayerDebugException { if (args.length != 1) { return DValue.forPrimitive(DValue.UNDEFINED, isolateId); } double arg = ECMA.toNumber(this, args[0]); long id = (long) arg; if (id != arg) { return DValue.forPrimitive(DValue.UNDEFINED, isolateId); } DValue value = m_manager.getValue(id, isolateId); if (value == null) { return DValue.forPrimitive(DValue.UNDEFINED, isolateId); } return value; } public Value callConstructor(String funcname, Value[] args) throws PlayerDebugException { return callConstructorWorker(funcname, args, Isolate.DEFAULT_ID); } public Value callConstructorWorker(String funcname, Value[] args, int isolateId) throws PlayerDebugException { return callFunction(DValue.forPrimitive(null, isolateId), true, funcname, args, isolateId); } private DMessage buildCallFunctionMessage(boolean isConstructor, Value thisValue, String funcname, Value[] args) { funcname = (funcname == null) ? "" : funcname; //$NON-NLS-1$ int messageSize = 8; // DWORD representing flags + DWORD representing frame String thisType = DVariable.typeNameFor(thisValue.getType()); String thisValueString = thisValue.getValueAsString(); messageSize += DMessage.getStringLength(thisType)+1; messageSize += DMessage.getStringLength(thisValueString)+1; messageSize += DMessage.getStringLength(funcname)+1; messageSize += 4; // DWORD representing the number of args String[] argTypes = new String[args.length]; String[] argValues = new String[args.length]; for (int i=0; i<args.length; ++i) { argTypes[i] = DVariable.typeNameFor(args[i].getType()); argValues[i] = args[i].getValueAsString(); messageSize += DMessage.getStringLength(argValues[i])+1; messageSize += DMessage.getStringLength(argTypes[i])+1; } DMessage dm = DMessageCache.alloc(messageSize); dm.setType(DMessage.OutCallFunction); try { dm.putDWord(isConstructor ? 1 : 0); dm.putDWord(0); // TODO: the currently active frame number dm.putString(thisType); dm.putString(thisValueString); dm.putString(funcname); dm.putDWord(args.length); for (int i=0; i<args.length; ++i) { dm.putString(argTypes[i]); dm.putString(argValues[i]); } } catch(UnsupportedEncodingException uee) { // couldn't write out the string, so just terminate it and complete anyway dm.putByte((byte)'\0'); } return dm; } private void requestVariable(long id, String name, boolean fireGetter, boolean alsoGetChildren, int isolateId) throws NoResponseException, NotConnectedException, NotSuspendedException { if (!isWorkerSuspended(isolateId)) throw new NotSuspendedException(); name = getRawMemberName(id, name, isolateId); DMessage dm = buildOutGetMessage(id, name, fireGetter, alsoGetChildren); dm.setTargetIsolate(isolateId); // make sure any exception during the setter gets held onto m_manager.beginPlayerCodeExecution(isolateId); int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT); timeout += 500; // give the player enough time to raise its timeout exception boolean result = simpleRequestResponseMessage(dm, DMessage.InGetVariable, timeout); // tell manager we're done; ignore returned FaultEvent m_manager.endPlayerCodeExecution(isolateId); if (!result) throw new NoResponseException(timeout); } private DMessage buildOutGetMessage(long id, String name, boolean fireGetter, boolean alsoGetChildren) { final int FLAGS_SIZE = 4; name = (name == null) ? "" : name; //$NON-NLS-1$ DMessage dm = DMessageCache.alloc(DMessage.getSizeofPtr() + DMessage.getStringLength(name)+1 + FLAGS_SIZE); dm.setType( (!fireGetter) ? DMessage.OutGetVariable : DMessage.OutGetVariableWhichInvokesGetter ); dm.putPtr(id); try { dm.putString(name); } catch(UnsupportedEncodingException uee) { // couldn't write out the string, so just terminate it and complete anyway dm.putByte((byte)'\0'); } // as an optimization, newer player builds allow us to tell them not to // send all the children of an object along with the object, because // frequently we don't care about the children int flags = GetVariableFlag.DONT_GET_FUNCTIONS; // we never want functions if (fireGetter) flags |= GetVariableFlag.INVOKE_GETTER; if (alsoGetChildren) flags |= GetVariableFlag.ALSO_GET_CHILDREN | GetVariableFlag.GET_CLASS_HIERARCHY; dm.putDWord(flags); return dm; } public FaultEvent setScalarMember(long varId, String memberName, int type, String value, int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException { if (!isWorkerSuspended(isolateId)) throw new NotSuspendedException(); // If the varId is that of a stack frame, then we need to check whether that // stack frame has an "activation object". If it does, then all of the // arguments and locals are actually kept as members of that activation // object, and so we need to change varId to be the ID of that activation // object -- that way, the player will modify the member of the activation // object rather than modifying the "regular" argument or local. See bug // 155031. if (varId <= Value.BASE_ID && varId > Value.LEVEL_ID) { int depth = (int) (Value.BASE_ID - varId); DStackContext context = m_manager.getFrame(depth,isolateId); DVariable activationObject = context.getActivationObject(); if (activationObject != null) varId = activationObject.getValue().getId(); } memberName = getRawMemberName(varId, memberName, isolateId); // see if it is our any of our special variables FaultEvent faultEvent = requestSetVariable( isPseudoVarId(varId) ? 0 : varId, memberName, type, value, isolateId); // now that we sent it out, we need to clear our variable cache // if it is our special context then mark the frame as stale. if (isPseudoVarId(varId) && m_manager.getFrameCount(isolateId) > 0) { m_manager.getFrame(0, isolateId).markStale(); } else { DValue parent = m_manager.getValue(varId,isolateId); if (parent != null) parent.removeAllMembers(); } return faultEvent; } /** * Returns whether a variable ID is "real" or not. For example, * Value.THIS_ID is a "pseudo" varId, as are all the other special * hard-coded varIds in the Value class. */ private boolean isPseudoVarId(long varId) { /* * Unfortunately, this is actually just taking a guess. The old code * used "varId < 0"; however, the Linux player sometimes has real * variable IDs which are less than zero. */ return (varId < 0 && varId > -65535); } /** * <code>memberName</code> might be just <code>"varname"</code>, or it * might be <code>"namespace::varname"</code>, or it might be * <code>"namespace@hexaddr::varname"</code>. In the third case, it is * fully resolved, and there is nothing we need to do. But in the first * and second cases, we may need to fully resolve it so that the Player * will recognize it. */ private String getRawMemberName(long parentValueId, String memberName, int isolateId) { if (memberName != null) { DValue parent = m_manager.getValue(parentValueId, isolateId); if (parent != null) { int doubleColon = memberName.indexOf("::"); //$NON-NLS-1$ String shortName = (doubleColon==-1) ? memberName : memberName.substring(doubleColon+2); DVariable member = parent.findMember(shortName); if (member != null) memberName = member.getRawName(); } } return memberName; } /** * @return null for success, or fault event if a setter in the player threw an exception */ private FaultEvent requestSetVariable(long id, String name, int t, String value, int isolateId) throws NoResponseException { // convert type to typeName String type = DVariable.typeNameFor(t); DMessage dm = buildOutSetMessage(id, name, type, value); dm.setTargetIsolate(isolateId); FaultEvent faultEvent = null; // System.out.println("setmsg id="+id+",name="+name+",t="+type+",value="+value); // make sure any exception during the setter gets held onto m_manager.beginPlayerCodeExecution(isolateId); // turn off squelch so we can hear the response sendSquelch(false, isolateId); int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT); if (!simpleRequestResponseMessage(dm, (t == VariableType.STRING) ? DMessage.InSetVariable : DMessage.InSetVariable2, timeout)) throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); // turn it back on sendSquelch(true, isolateId); // tell manager we're done, and get exception if any faultEvent = m_manager.endPlayerCodeExecution(isolateId); // hammer the variable cache and context array m_manager.freeValueCache(isolateId); return faultEvent; } private DMessage buildOutSetMessage(long id, String name, String type, String v) { DMessage dm = DMessageCache.alloc(DMessage.getSizeofPtr()+ DMessage.getStringLength(name)+ DMessage.getStringLength(type)+ DMessage.getStringLength(v)+ 3); dm.setType(DMessage.OutSetVariable); dm.putPtr(id); try { dm.putString(name); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); } try { dm.putString(type); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); } try { dm.putString(v); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); } return dm; } /* * @see flash.tools.debugger.Session#waitForEvent() */ public void waitForEvent() throws NotConnectedException, InterruptedException { Object eventNotifier = m_manager.getEventNotifier(); synchronized (eventNotifier) { while (getEventCount() == 0 && isConnected()) { eventNotifier.wait(); } } // We should NOT call isConnected() to test for a broken connection! That // is because we may have received one or more events AND lost the connection, // almost simultaneously. If there are any messages available for the // caller to process, we should not throw an exception. if (getEventCount() == 0 && !isConnected()) throw new NotConnectedException(); } /* * @see flash.tools.debugger.Session#getEventCount() */ public int getEventCount() { return m_manager.getEventCount(); } /* * @see flash.tools.debugger.Session#nextEvent() */ public DebugEvent nextEvent() { return m_manager.nextEvent(); } /** * Adds a watchpoint on the given expression * @throws NotConnectedException * @throws NoResponseException * @throws NotSupportedException * @throws NotSuspendedException */ public boolean addWatch(long varId, String varName, int type, int tag, int isolateId) throws NoResponseException, NotConnectedException, NotSupportedException { // TODO check for NoResponse, NotConnected if (!supportsWatchpoints(isolateId)) throw new NotSupportedException(PlayerSessionManager.getLocalizationManager().getLocalizedTextString("watchpointsNotSupported")); //$NON-NLS-1$ varName = getRawMemberName(varId, varName, isolateId); DMessage dm = DMessageCache.alloc(4+DMessage.getSizeofPtr()+DMessage.getStringLength(varName)+1); dm.setType(DMessage.OutAddWatch2); dm.setTargetIsolate(isolateId); dm.putPtr(varId); try { dm.putString(varName); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); } dm.putWord(type); dm.putWord(tag); int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT); boolean result = simpleRequestResponseMessage(dm, DMessage.InWatch2, timeout); return result; } /** * Removes a watchpoint on the given expression * @throws NotConnectedException * @throws NoResponseException * @throws NotSuspendedException */ public boolean removeWatch(long varId, String memberName, int isolateId) throws NoResponseException, NotConnectedException { memberName = getRawMemberName(varId, memberName, isolateId); DMessage dm = DMessageCache.alloc(DMessage.getSizeofPtr()+DMessage.getStringLength(memberName)+1); dm.setType(DMessage.OutRemoveWatch2); dm.putPtr(varId); try { dm.putString(memberName); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); } int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT); boolean result = simpleRequestResponseMessage(dm, DMessage.InWatch2, timeout); return result; } /** * Send a message that contains no data */ void sendMessage(int message) { DMessage dm = DMessageCache.alloc(0); dm.setType(message); sendMessage(dm); } /** * Send a message that contains no data */ void sendMessageIsolate(int message, int isolateId) { DMessage dm = DMessageCache.alloc(0); dm.setTargetIsolate(isolateId); dm.setType(message); sendMessage(dm); } /** * Send a fully formed message and release it when done */ synchronized void sendMessage(DMessage dm) { try { if (dm.getType() != DMessage.OutSetActiveIsolate) { int isolate = dm.getTargetIsolate(); if (isolate != getActiveIsolate().getId()) { DMessage dm1 = DMessageCache.alloc(4); dm1.setTargetIsolate(isolate); dm1.setType(DMessage.OutSetActiveIsolate); dm1.putDWord(isolate); /* Use sendMessage here to avoid waiting for a response. * The assumption is that once the message is sent, subsequent * messages are for that isolate regardless of the player confirming * it. With this change, performance has improved considerably; player * debugger has not gone out of sync since the ProcessTag messages * flood issue was resolved. */ sendMessage(dm1); m_manager.setActiveIsolate(m_manager.getIsolate(isolate)); } } m_protocol.txMessage(dm); if (m_debugMsgOn || m_debugMsgFileOn) trace(dm, false); } catch(IOException io) { if (Trace.error) { Trace.trace("Attempt to send message "+dm.outToString()+" failed"); //$NON-NLS-1$ //$NON-NLS-2$ io.printStackTrace(); } } DMessageCache.free(dm); } /** * Tell the player to shut-up */ boolean sendSquelch(boolean on, int isolateId) { boolean responded; DMessage dm = DMessageCache.alloc(4); dm.setType(DMessage.OutSetSquelch); dm.setTargetIsolate(isolateId); dm.putDWord( on ? 1 : 0); responded = simpleRequestResponseMessage(dm, DMessage.InSquelch); return responded; } void sendStopWarning() { // Currently, "disable_script_stuck_dialog" only works for AS2, not for AS3. String option = "disable_script_stuck_dialog"; //$NON-NLS-1$ String value = "on"; //$NON-NLS-1$ sendOptionMessage(option, value); // HACK: Completely disable the script-stuck notifications, so that we can // get AS3 debugging working. option = "disable_script_stuck"; //$NON-NLS-1$ value = "on"; //$NON-NLS-1$ sendOptionMessage(option, value); } void sendStopOnFault() { String option = "break_on_fault"; //$NON-NLS-1$ String value = "on"; //$NON-NLS-1$ sendOptionMessage(option, value); } void sendEnumerateOverride() { String option = "enumerate_override"; //$NON-NLS-1$ String value = "on"; //$NON-NLS-1$ sendOptionMessage(option, value); } void sendFailureNotify() { String option = "notify_on_failure"; //$NON-NLS-1$ String value = "on"; //$NON-NLS-1$ sendOptionMessage(option, value); } void sendInvokeSetters() { String option = "invoke_setters"; //$NON-NLS-1$ String value = "on"; //$NON-NLS-1$ sendOptionMessage(option, value); } void sendSwfloadNotify() { String option = "swf_load_messages"; //$NON-NLS-1$ String value = "on"; //$NON-NLS-1$ sendOptionMessage(option, value); } void sendConsoleErrorsAsTrace(boolean on) { String option = "console_errors"; //$NON-NLS-1$ String value = (on) ? "on" : "off"; //$NON-NLS-1$ //$NON-NLS-2$ sendOptionMessage(option, value); } void sendGetterTimeout() { String option = "getter_timeout"; //$NON-NLS-1$ String value = "" + getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT); //$NON-NLS-1$ sendOptionMessage(option, value); } void sendSetterTimeout() { String option = "setter_timeout"; //$NON-NLS-1$ String value = "" + getPreference(SessionManager.PREF_SETVAR_RESPONSE_TIMEOUT); //$NON-NLS-1$ sendOptionMessage(option, value); } void sendConcurrentDebugger() { String option = "concurrent_debugger"; //$NON-NLS-1$ String value = "on"; //$NON-NLS-1$ sendOptionMessage(option, value); } void sendWideLineDebugger() { String option = "wide_line_debugger"; //$NON-NLS-1$ String value = "on"; //$NON-NLS-1$ sendOptionMessage(option, value); m_manager.setWideLines(true); } void sendOptionMessage(String option, String value) { int msgSize = DMessage.getStringLength(option)+DMessage.getStringLength(value)+2; // add 2 for trailing nulls of each string DMessage dm = DMessageCache.alloc(msgSize); dm.setType(DMessage.OutSetOption); try { dm.putString(option); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); } try { dm.putString(value); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); } simpleRequestResponseMessage(dm, DMessage.InOption); } public boolean supportsWatchpoints() { return supportsWatchpoints(Isolate.DEFAULT_ID); } public boolean supportsWatchpoints(int isolateId) { if (m_playerSupportsWatchpoints == null) m_playerSupportsWatchpoints = new Boolean(getOption("can_set_watchpoints", false, isolateId)); //$NON-NLS-1$ return m_playerSupportsWatchpoints.booleanValue(); } public boolean playerCanBreakOnAllExceptions() { return playerCanBreakOnAllExceptions(Isolate.DEFAULT_ID); } public boolean playerCanBreakOnAllExceptions(int isolateId) { if (m_playerCanBreakOnAllExceptions == null) m_playerCanBreakOnAllExceptions = new Boolean(getOption("can_break_on_all_exceptions", false, isolateId)); //$NON-NLS-1$ return m_playerCanBreakOnAllExceptions.booleanValue(); } public boolean supportsConcurrency(int isolateId) { if (m_playerSupportsConcurrency == null) m_playerSupportsConcurrency = new Boolean(getOption("concurrent_player", false, isolateId)); //$NON-NLS-1$ return m_playerSupportsConcurrency.booleanValue(); } public boolean supportsConcurrency() { return supportsConcurrency(Isolate.DEFAULT_ID); } public boolean supportsWideLineNumbers() { return supportsWideLineNumbers(Isolate.DEFAULT_ID); } public boolean supportsWideLineNumbers(int isolateId) { if (m_playerSupportsWideLine == null) m_playerSupportsWideLine = new Boolean(getOption("wide_line_player", false, isolateId)); //$NON-NLS-1$ return m_playerSupportsWideLine.booleanValue(); } public boolean playerCanTerminate() { return getOption("can_terminate", false, Isolate.DEFAULT_ID); //$NON-NLS-1$ } public boolean playerCanCallFunctions() { return playerCanCallFunctions(Isolate.DEFAULT_ID); } public boolean playerCanCallFunctions(int isolateId) { if (m_playerCanCallFunctions == null) m_playerCanCallFunctions = new Boolean(getOption("can_call_functions", false, isolateId)); //$NON-NLS-1$ return m_playerCanCallFunctions.booleanValue(); } /** * Returns the value of a Flash Player boolean option that was requested by * OutGetOption and returned by InOption. * * @param optionName * the name of the option * @return its value, or null */ public boolean getOption(String optionName, boolean defaultValue, int isolateId) { boolean retval = defaultValue; String optionValue = getOption(optionName, null, isolateId); if (optionValue != null) retval = Boolean.valueOf(optionValue).booleanValue(); return retval; } /** * Returns the value of a Flash Player string 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, String defaultValue, int isolateId) { String optionValue = defaultValue; int msgSize = DMessage.getStringLength(optionName)+1; // add 1 for trailing null of string DMessage dm = DMessageCache.alloc(msgSize); dm.setTargetIsolate(isolateId); dm.setType(DMessage.OutGetOption); try { dm.putString(optionName); } catch(UnsupportedEncodingException uee) { dm.putByte((byte)'\0'); } if (simpleRequestResponseMessage(dm, DMessage.InOption)) optionValue = m_manager.getOption(optionName); return optionValue; } long getMessageInCount(DMessageCounter counter, long isolate, int msgType) { if (isolate == Isolate.DEFAULT_ID) { return counter.getInCount(msgType); } else { return counter.getIsolateInCount(isolate, msgType); } } Object getMessageInLock(DMessageCounter counter, long isolate) { if (isolate == Isolate.DEFAULT_ID) { return counter.getInLock(); } else { return counter.getIsolateInLock(isolate); } } /** * Send our message and assume that the next response that is received is * ours. Primitive but there is no use in setting up a full request / response * pattern since the player doesn't follow it. * * @return false is no response. */ boolean simpleRequestResponseMessage(DMessage msg, int msgType, int timeout) { boolean response = false; //FIXME: Check if timeout needs to adjust to the isolate switching // delay // use default or user supplied timeout timeout = (timeout > 0) ? timeout : getPreference(SessionManager.PREF_RESPONSE_TIMEOUT); // note the number of messages of this type before our send DMessageCounter msgCounter = getMessageCounter(); int isolate = msg.getTargetIsolate(); long num = getMessageInCount(msgCounter, isolate, msgType); long expect = num+1; // send the message sendMessage(msg); long startTime = System.currentTimeMillis(); // System.out.println("sending- "+DMessage.outTypeName(msg.getType())+",timeout="+timeout+",start="+start); // now wait till we see a message come in m_incoming = false; synchronized (getMessageInLock(msgCounter, isolate)) { while( (expect > getMessageInCount(msgCounter, isolate, msgType)) && System.currentTimeMillis() < startTime + timeout && isConnected()) { // block until the message counter tells us that some message has been received try { getMessageInLock(msgCounter, isolate).wait(timeout); } catch (InterruptedException e) { // this should never happen e.printStackTrace(); //FIXME: Will resetting the interrupted status here //cause any problems? // Thread.currentThread().interrupt(); } // if we see incoming messages, then we should reset our timeout synchronized (this) { if (m_incoming) { startTime = System.currentTimeMillis(); m_incoming = false; } } } } if (getMessageInCount(msgCounter, isolate, msgType) >= expect) response = true; else if (timeout <= 0 && Trace.error) Trace.trace("Timed-out waiting for "+DMessage.inTypeName(msgType)+" response to message "+msg.outToString()); //$NON-NLS-1$ //$NON-NLS-2$ // long endTime = System.currentTimeMillis(); // System.out.println(" response- "+response+",timeout="+timeout+",elapsed="+(endTime-startTime)); m_lastResponse = response; return response; } // use default timeout boolean simpleRequestResponseMessage(DMessage msg, int msgType) { return simpleRequestResponseMessage(msg, msgType, -1); } boolean simpleRequestResponseMessageIsolate(DMessage msg, int msgType, int isolateId) { return simpleRequestResponseMessageIsolate(msg, msgType, -1, isolateId); } boolean simpleRequestResponseMessageIsolate(DMessage msg, int msgType, int timeout, int isolateId) { msg.setTargetIsolate(isolateId); return simpleRequestResponseMessage(msg, msgType, timeout); } boolean simpleRequestResponseMessage(int msg, int msgType) { return simpleRequestResponseMessage(msg, msgType, -1); } boolean simpleRequestResponseMessageIsolate(int msg, int msgType, int isolateId) { return simpleRequestResponseMessageIsolate(msg, msgType, -1, isolateId); } boolean simpleRequestResponseMessageIsolate(int msg, int msgType, int timeout, int isolateId) { DMessage dm = DMessageCache.alloc(0); dm.setType(msg); dm.setTargetIsolate(isolateId); return simpleRequestResponseMessage(dm, msgType, timeout); } // Convenience function boolean simpleRequestResponseMessage(int msg, int msgType, int timeout) { DMessage dm = DMessageCache.alloc(0); dm.setType(msg); return simpleRequestResponseMessage(dm, msgType, timeout); } /** * We register ourself as a listener to DMessages from the pipe for the * sole purpose of monitoring the state of the debugger. All other * object management occurs with DManager */ /** * Issued when the socket connection to the player is cut */ public void disconnected() { m_isHalted = false; m_isConnected = false; m_manager.disconnected(); } /** * 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) { preMessageArrived(msg, which); msg.reset(); // allow the message to be re-parsed m_manager.messageArrived(msg, which); msg.reset(); // allow the message to be re-parsed postMessageArrived(msg, which); } /** * Processes the message before it is passed to the DManager. */ private void preMessageArrived(DMessage msg, DProtocol which) { switch (msg.getType()) { case DMessage.InIsolate: m_lastPreIsolate = (int)msg.getDWord(); break; case DMessage.InAskBreakpoints: case DMessage.InBreakAt: case DMessage.InBreakAtExt: { // We need to set m_isHalted to true before the DManager processes // the message, because the DManager may add a BreakEvent to the // event queue, which the host debugger may immediately process; // if the debugger calls back to the Session, the Session must be // correctly marked as halted. if (m_lastPreIsolate == Isolate.DEFAULT_ID) m_isHalted = true; else updateHaltIsolateStatus(m_lastPreIsolate, true); break; } } } /** * Processes the message after it has been passed to the DManager. */ private void postMessageArrived(DMessage msg, DProtocol which) { if (m_debugMsgOn || m_debugMsgFileOn) trace(msg, true); /* at this point we just open up a big switch statement and walk through all possible cases */ int type = msg.getType(); switch(type) { case DMessage.InExit: { m_isConnected = false; break; } case DMessage.InProcessTag: { // need to send a response to this message to keep the player going sendMessageIsolate(DMessage.OutProcessedTag, msg.getTargetIsolate()); break; } case DMessage.InContinue: { if (msg.getTargetIsolate() == Isolate.DEFAULT_ID) m_isHalted = false; else { updateHaltIsolateStatus(msg.getTargetIsolate(), false); } break; } case DMessage.InOption: { //workers inherit options, so only store options //from main thread. if (msg.getTargetIsolate() == Isolate.DEFAULT_ID) { String s = msg.getString(); String v = msg.getString(); // add it to our properties, for DEBUG purposes only m_prefs.put(s, v); } break; } case DMessage.InSwfInfo: case DMessage.InScript: case DMessage.InRemoveScript: { //FIXME: Clear this cache only for affected //workers. Right now, the key contains worker //id, so we are safe. But we unnecessarily flush //the queue. m_evalIsAndInstanceofCache.clear(); m_incoming = true; break; } default: { /** * Simple indicator that we have received a message. We * put this indicator in default so that InProcessTag msgs * wouldn't generate false triggers. Mainly, we want to * reset our timeout counter when we receive trace messages. */ m_incoming = true; break; } } // something came in so assume that we can now talk // to the player m_lastResponse = true; } private void updateHaltIsolateStatus(int targetIsolate, boolean value) { if (!m_isolateStatus.containsKey(targetIsolate)) { PlayerSessionIsolateStatus status = new PlayerSessionIsolateStatus(); status.m_isHalted = value; m_isolateStatus.put(targetIsolate, status); } else { m_isolateStatus.get(targetIsolate).m_isHalted = value; } } /** * A background thread which wakes up periodically and fetches the SWF and SWD * from the Player for new movies that have loaded. It then uses these to create * an instance of MovieMetaData (a class shared with the Profiler) from which * fdb can cull function names. * This work is done on a background thread because it can take several * seconds, and we want the fdb user to be able to execute other commands * while it is happening. */ public void run() { long last = 0; while(isConnected()) { // try every 250ms try { Thread.sleep(250); } catch(InterruptedException ie) {} try { // let's make sure that the traffic level is low before // we do our requests. long current = m_protocol.messagesReceived(); long delta = last - current; last = current; // if the last message that went out was not responded to // or we are not suspended and have high traffic // then wait for later. if (!m_lastResponse || (!isSuspended() && delta > 5)) throw new NotSuspendedException(); // we are either suspended or low enough traffic // get the list of swfs we have for (Isolate isolate : m_manager.getIsolates()) { int isolateId = isolate.getId(); if (isolateId != Isolate.DEFAULT_ID && !isWorkerSuspended(isolateId) && delta > 5) { throw new NotSuspendedException(); } int count = m_manager.getSwfInfoCount(isolateId); for(int i=0; i<count; i++) { DSwfInfo info = m_manager.getSwfInfo(i, isolateId); // no need to process if it's been removed if (info == null || info.isUnloaded() || info.isPopulated() || (info.getVmVersion() > 0) ) continue; // see if the swd has been loaded, throws exception if unable to load it. // Also triggers a callback into the info object to freshen its contents // if successful //FIXME: remove sysout info.getSwdSize(this); // check since our vm version info could get updated in between. if (info.getVmVersion() > 0) { // mark it populated if we haven't already done so info.setPopulated(); continue; } // so by this point we know that we've got good swd data, // or we've made too many attempts and gave up. if (!info.isSwdLoading() && !info.isUnloaded()) { // now load the swf, if we haven't already got it if (info.getSwf() == null && !info.isUnloaded()) info.setSwf(requestSwf(i)); // only get the swd if we haven't got it if (info.getSwd() == null && !info.isUnloaded()) info.setSwd(requestSwd(i)); try { // now go populate the functions tables... if (!info.isUnloaded()) info.parseSwfSwd(m_manager); } catch(Exception e) { // oh this is not good and means that we should probably // give up. if (Trace.error) { Trace.trace("Error while parsing swf/swd '"+info.getUrl()+"'. Giving up and marking it processed"); //$NON-NLS-1$ //$NON-NLS-2$ e.printStackTrace(); } info.setPopulated(); } } } } } catch(InProgressException ipe) { // swd is still loading so give us a bit of // time and then come back and try again } catch(NoResponseException nre) { // timed out on one of our requests so don't bother // continuing right now, try again later } catch(NotSuspendedException nse) { // probably want to wait until we are halted before // doing this heavy action } catch(Exception e) { // maybe not good if (Trace.error) { Trace.trace("Exception in background swf/swd processing thread"); //$NON-NLS-1$ e.printStackTrace(); } } } } byte[] requestSwf(int index) throws NoResponseException { /* send the message */ int to = getPreference(SessionManager.PREF_SWFSWD_LOAD_TIMEOUT); byte[] swf = null; // the query DMessage dm = DMessageCache.alloc(2); dm.setType(DMessage.OutGetSwf); dm.putWord(index); if (simpleRequestResponseMessage(dm, DMessage.InGetSwf, to)) swf = m_manager.getSWF(); else throw new NoResponseException(to); return swf; } byte[] requestSwd(int index) throws NoResponseException { /* send the message */ int to = getPreference(SessionManager.PREF_SWFSWD_LOAD_TIMEOUT); byte[] swd = null; // the query DMessage dm = DMessageCache.alloc(2); dm.setType(DMessage.OutGetSwd); dm.putWord(index); if (simpleRequestResponseMessage(dm, DMessage.InGetSwd, to)) swd = m_manager.getSWD(); else throw new NoResponseException(to); return swd; } // // Debug purposes only. Dump contents of our messages to the screen // and/or file. // synchronized void trace(DMessage dm, boolean in) { try { if (m_debugMsgOn) { System.out.println( (in) ? dm.inToString(m_debugMsgSize) : dm.outToString(m_debugMsgSize) ); } if (m_debugMsgFileOn) { traceFile().write( (in) ? dm.inToString(m_debugMsgFileSize) : dm.outToString(m_debugMsgFileSize) ); m_trace.write(s_newline); m_trace.flush(); } } catch(Exception e) {} } // i/o for tracing java.io.Writer m_trace; java.io.Writer traceFile() throws IOException { if (m_trace == null) { m_trace = new java.io.FileWriter("mm_debug_api_trace.txt"); //$NON-NLS-1$ try { m_trace.write(new java.util.Date().toString()); } catch(Exception e) { m_trace.write("Date unknown"); } //$NON-NLS-1$ try { m_trace.write(s_newline); // java properties dump java.util.Properties props = System.getProperties(); props.list(new java.io.PrintWriter(m_trace)); m_trace.write(s_newline); // property dump for (String key: m_prefs.keySet()) { Object value = m_prefs.get(key); m_trace.write(key); m_trace.write(" = "); //$NON-NLS-1$ m_trace.write(value.toString()); m_trace.write(s_newline); } } catch(Exception e) { if (Trace.error) e.printStackTrace(); } m_trace.write(s_newline); } return m_trace; } public void setLaunchUrl(String url) { if (url.startsWith("/")) { //$NON-NLS-1$ url = "file://" + url; //$NON-NLS-1$ } m_launchUrl = url; } public void setAIRLaunchInfo(AIRLaunchInfo airLaunchInfo) { m_airLaunchInfo = airLaunchInfo; } public void breakOnCaughtExceptions(boolean b) throws NotSupportedException, NoResponseException { breakOnCaughtExceptions(b, Isolate.DEFAULT_ID); } public void breakOnCaughtExceptions(boolean b, int isolateId) throws NotSupportedException, NoResponseException { if (!playerCanBreakOnAllExceptions(isolateId)) throw new NotSupportedException(PlayerSessionManager.getLocalizationManager().getLocalizedTextString("exceptionBreakpointsNotSupported")); //$NON-NLS-1$ DMessage dm = DMessageCache.alloc(1); dm.setType(DMessage.OutPassAllExceptionsToDebugger); dm.putByte((byte)(b ? 1 : 0)); dm.setTargetIsolate(isolateId); /* TODO: Verify that sendMessage below is a bug */ // sendMessage(dm); if (!simpleRequestResponseMessage(dm, DMessage.InPassAllExceptionsToDebugger)) throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); } public boolean evalIs(Value value, Value type) throws PlayerDebugException, PlayerFaultException { return evalIsOrInstanceof(BinaryOp.Is, value, type, Isolate.DEFAULT_ID); } public boolean evalIs(Value value, String type) throws PlayerDebugException, PlayerFaultException { return evalIsOrInstanceof(BinaryOp.Is, value, type, Isolate.DEFAULT_ID); } public boolean evalInstanceof(Value value, Value type) throws PlayerDebugException, PlayerFaultException { return evalIsOrInstanceof(BinaryOp.Instanceof, value, type, Isolate.DEFAULT_ID); } public boolean evalInstanceof(Value value, String type) throws PlayerDebugException, PlayerFaultException { return evalIsOrInstanceof(BinaryOp.Instanceof, value, type, Isolate.DEFAULT_ID); } // isolate version public boolean evalIsWorker(Value value, Value type, int isolateId) throws PlayerDebugException, PlayerFaultException { return evalIsOrInstanceof(BinaryOp.Is, value, type, isolateId); } public boolean evalIsWorker(Value value, String type, int isolateId) throws PlayerDebugException, PlayerFaultException { return evalIsOrInstanceof(BinaryOp.Is, value, type, isolateId); } public boolean evalInstanceofWorker(Value value, Value type, int isolateId) throws PlayerDebugException, PlayerFaultException { return evalIsOrInstanceof(BinaryOp.Instanceof, value, type, isolateId); } public boolean evalInstanceofWorker(Value value, String type, int isolateId) throws PlayerDebugException, PlayerFaultException { return evalIsOrInstanceof(BinaryOp.Instanceof, value, type, isolateId); } private boolean evalIsOrInstanceof(BinaryOp op, Value value, Value type, int isolateId) throws PlayerDebugException, PlayerFaultException { String key = value.getTypeName() + " " + op + " " + type.getTypeName() + " " + String.valueOf(isolateId); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ Boolean retval = m_evalIsAndInstanceofCache.get(key); if (retval == null) { retval = new Boolean(ECMA.toBoolean(evalBinaryOp(op, value, type, isolateId))); m_evalIsAndInstanceofCache.put(key, retval); } return retval.booleanValue(); } private boolean evalIsOrInstanceof(BinaryOp op, Value value, String type, int isolateId) throws PlayerDebugException, PlayerFaultException { String key = value.getTypeName() + " " + op + " " + type + " " + String.valueOf(isolateId); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ Boolean retval = m_evalIsAndInstanceofCache.get(key); if (retval == null) { Value typeval = getGlobalWorker(type, isolateId); if (typeval == null) retval = Boolean.FALSE; else retval = new Boolean(ECMA.toBoolean(evalBinaryOp(op, value, typeval, isolateId))); m_evalIsAndInstanceofCache.put(key, retval); } return retval.booleanValue(); } public boolean evalIn(Value property, Value object) throws PlayerDebugException, PlayerFaultException { return ECMA.toBoolean(evalBinaryOp(BinaryOp.In, property, object, Isolate.DEFAULT_ID)); } public Value evalAs(Value value, Value type) throws PlayerDebugException, PlayerFaultException { return evalBinaryOp(BinaryOp.As, value, type, Isolate.DEFAULT_ID); } private Value evalBinaryOp(BinaryOp op, Value lhs, Value rhs, int isolateId) throws PlayerDebugException, PlayerFaultException { if (!isWorkerSuspended(isolateId)) throw new NotSuspendedException(); if (!playerCanCallFunctions(isolateId)) { Map<String,String> parameters = new HashMap<String,String>(); parameters.put("operator", op.getName()); //$NON-NLS-1$ String message = PlayerSessionManager.getLocalizationManager().getLocalizedTextString("operatorNotSupported", parameters); //$NON-NLS-1$ throw new NotSupportedException(message); } int id = (int) (Math.random() * 65536); // good 'nuff DMessage dm = buildBinaryOpMessage(id, op, lhs, rhs); dm.setTargetIsolate(isolateId); m_manager.clearLastBinaryOp(isolateId); // make sure any exception gets held onto m_manager.beginPlayerCodeExecution(isolateId); // TODO wrong timeout int timeout = getPreference(SessionManager.PREF_GETVAR_RESPONSE_TIMEOUT); timeout += 500; // give the player enough time to raise its timeout exception boolean result = simpleRequestResponseMessage(dm, DMessage.InBinaryOp, timeout); // tell manager we're done; ignore returned FaultEvent m_manager.endPlayerCodeExecution(isolateId); if (!result) throw new NoResponseException(timeout); DVariable lastBinaryOp = m_manager.lastBinaryOp(isolateId); Value v; if (lastBinaryOp != null) v = lastBinaryOp.getValue(); else v = DValue.forPrimitive(Value.UNDEFINED, isolateId); if (v.isAttributeSet(ValueAttribute.IS_EXCEPTION)) throw new PlayerFaultException(new ExceptionFault(v.getValueAsString(), false, v, isolateId)); return v; } private DMessage buildBinaryOpMessage(int id, BinaryOp op, Value lhs, Value rhs) { int messageSize = 5; // DWORD representing id + byte representing op String lhsType = DVariable.typeNameFor(lhs.getType()); String lhsValueString = lhs.getValueAsString(); String rhsType = DVariable.typeNameFor(rhs.getType()); String rhsValueString = rhs.getValueAsString(); messageSize += DMessage.getStringLength(lhsType)+1; messageSize += DMessage.getStringLength(lhsValueString)+1; messageSize += DMessage.getStringLength(rhsType)+1; messageSize += DMessage.getStringLength(rhsValueString)+1; DMessage dm = DMessageCache.alloc(messageSize); dm.setType(DMessage.OutBinaryOp); try { dm.putDWord(id); dm.putByte((byte) op.getValue()); dm.putString(lhsType); dm.putString(lhsValueString); dm.putString(rhsType); dm.putString(rhsValueString); } catch(UnsupportedEncodingException uee) { // couldn't write out the string, so just terminate it and complete anyway dm.putByte((byte)'\0'); } return dm; } /* (non-Javadoc) * @see flash.tools.debugger.Session#getDisconnectCause() */ public Exception getDisconnectCause() { if (m_protocol != null) return m_protocol.getDisconnectCause(); return null; } /* (non-Javadoc) * @see flash.tools.debugger.Session#refreshIsolates() */ public Isolate[] refreshWorkers() throws NotSupportedException, NotSuspendedException, NoResponseException, NotConnectedException { if (!supportsConcurrency()) { throw new NotSupportedException(PlayerSessionManager.getLocalizationManager().getLocalizedTextString("concurrencyNotSupported")); //$NON-NLS-1$ } if (!isSuspended()) throw new NotSuspendedException(); boolean response = simpleRequestResponseMessage(DMessage.OutIsolateEnumerate, DMessage.InIsolateEnumerate); if (response) { return m_manager.getIsolates(); } return null; } private Isolate getActiveIsolate() { return m_manager.getActiveIsolate(); } @Override public Isolate[] getWorkers() { return m_manager.getIsolates(); } @Override public void resumeWorker(int isolateId) throws NotSuspendedException, NotConnectedException, NoResponseException { if (!isWorkerSuspended(isolateId)) throw new NotSuspendedException(); if (!simpleRequestResponseMessageIsolate(DMessage.OutContinue, DMessage.InContinue, isolateId)) throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); } @Override public int suspendReasonWorker(int isolateId) throws NotConnectedException { DSuspendInfo info = getSuspendInfoIsolate(isolateId); return info.getReason(); } public DSuspendInfo getSuspendInfoIsolate(int isolateId) { DSuspendInfo info = m_manager.getSuspendInfo(isolateId); if (info == null) { if (simpleRequestResponseMessageIsolate(DMessage.OutGetBreakReason, DMessage.InBreakReason, isolateId)) info = m_manager.getSuspendInfo(isolateId); // if we still can't get any info from the manager... if (info == null) info = new DSuspendInfo(); // empty unknown break information } return info; } @Override public void stepContinueWorker(int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException { if (!isWorkerSuspended(isolateId)) throw new NotSuspendedException(); // send a step-continue message and then wait for the Flash player to tell us that is has // resumed execution if (!simpleRequestResponseMessageIsolate(DMessage.OutStepContinue, DMessage.InContinue, isolateId)) throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); } @Override public Location setBreakpointWorker(int fileId, int lineNum, int isolateId) throws NoResponseException, NotConnectedException { /* send the message to the player and await a response */ Location l = null; int bp = DLocation.encodeId(fileId, lineNum); int wideLineSize = 0; if (supportsWideLineNumbers()) wideLineSize = 4; DMessage dm = DMessageCache.alloc(8 + wideLineSize); dm.setType(DMessage.OutSetBreakpoints); dm.putDWord(1); if (!supportsWideLineNumbers()) dm.putDWord(bp); else { dm.putDWord(fileId); dm.putDWord(lineNum); } boolean gotResponse = simpleRequestResponseMessageIsolate(dm, DMessage.InSetBreakpoint, isolateId); if(gotResponse) { /* even though we think we got an answer check that the breakpoint was added */ l = m_manager.getBreakpoint(bp, isolateId); } else throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); return l; } @Override public Frame[] getFramesWorker(int isolateId) throws NotConnectedException { return m_manager.getFrames(isolateId); } @Override public SwfInfo[] getSwfsWorker(int isolateId) throws NoResponseException { int swfCount = 0; swfCount = m_manager.getSwfInfoCount(isolateId); if (swfCount == 0) { // need to help out on the first one since the player // doesn't send it requestSwfInfo(0, isolateId); } //SwfInfo[] swfs = m_manager.getSwfInfos(); ArrayList<SwfInfo> swfList = new ArrayList<SwfInfo>(); for ( SwfInfo info : m_manager.getSwfInfos(isolateId) ) { swfList.add(info); } return swfList.toArray(new SwfInfo[0]); } @Override public void stepIntoWorker(int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException { if (isWorkerSuspended(isolateId)) { // send a step-into message and then wait for the Flash player to tell us that is has // resumed execution if (!simpleRequestResponseMessageIsolate(DMessage.OutStepInto, DMessage.InContinue, isolateId)) throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); } else throw new NotSuspendedException(); } @Override public void stepOutWorker(int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException { if (isWorkerSuspended(isolateId)) { // send a step-out message and then wait for the Flash player to tell us that is has // resumed execution if (!simpleRequestResponseMessageIsolate(DMessage.OutStepOut, DMessage.InContinue, isolateId)) throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); } else throw new NotSuspendedException(); } @Override public void stepOverWorker(int isolateId) throws NotSuspendedException, NoResponseException, NotConnectedException { if (isWorkerSuspended(isolateId)) { // send a step-over message and then wait for the Flash player to tell us that is has // resumed execution if (!simpleRequestResponseMessageIsolate(DMessage.OutStepOver, DMessage.InContinue, isolateId)) throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); } else throw new NotSuspendedException(); } public boolean evalInWorker(Value property, Value object, int isolateId) throws PlayerDebugException, PlayerFaultException { return ECMA.toBoolean(evalBinaryOp(BinaryOp.In, property, object, isolateId)); } public Value evalAsWorker(Value value, Value type, int isolateId) throws PlayerDebugException, PlayerFaultException { return evalBinaryOp(BinaryOp.As, value, type, isolateId); } @Override public void suspendWorker(int isolateId) throws SuspendedException, NotConnectedException, NoResponseException { // send a halt message int wait = getPreference(SessionManager.PREF_SUSPEND_WAIT); int every = 50; // wait this long for a response. The lower the number the more aggressive we are! if (isWorkerSuspended(isolateId)) throw new SuspendedException(); while (!isWorkerSuspended(isolateId) && wait > 0) { simpleRequestResponseMessageIsolate(DMessage.OutStopDebug, DMessage.InBreakAtExt, every, isolateId); wait -= every; } if (!isWorkerSuspended(isolateId)) throw new NoResponseException(wait); } @Override public IsolateSession getWorkerSession(int isolateId) { if (m_isolateSessions.containsKey(isolateId)) { return m_isolateSessions.get(isolateId); } else { IsolateSession workerSession = new IsolatePlayerSession(isolateId, this); m_isolateSessions.put(isolateId, workerSession); return workerSession; } } public boolean setExceptionBreakpoint(String exceptionClass) throws NoResponseException, NotConnectedException { return setExceptionBreakpointWorker(exceptionClass, Isolate.DEFAULT_ID); } public boolean setExceptionBreakpointWorker(String exceptionClass, int isolateId) throws NoResponseException, NotConnectedException { int messageSize = DMessage.getStringLength(exceptionClass) + 1; DMessage dm = DMessageCache.alloc(messageSize); dm.setType(DMessage.OutSetExceptionBreakpoint); dm.setTargetIsolate(isolateId); try { dm.putString(exceptionClass); } catch (UnsupportedEncodingException e) { // couldn't write out the string, so just terminate it and complete anyway dm.putByte((byte)'\0'); } boolean gotResponse = simpleRequestResponseMessageIsolate(dm, DMessage.InSetExceptionBreakpoint, isolateId); if(gotResponse) { return true; } else throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); } @Override public boolean clearExceptionBreakpoint(String exceptionClass) throws NoResponseException, NotConnectedException { return clearExceptionBreakpointWorker(exceptionClass, Isolate.DEFAULT_ID); } @Override public boolean clearExceptionBreakpointWorker(String exceptionClass, int isolateId) throws NoResponseException, NotConnectedException { int messageSize = DMessage.getStringLength(exceptionClass) + 1; DMessage dm = DMessageCache.alloc(messageSize); dm.setType(DMessage.OutRemoveExceptionBreakpoint); dm.setTargetIsolate(isolateId); try { dm.putString(exceptionClass); } catch (UnsupportedEncodingException e) { // couldn't write out the string, so just terminate it and complete anyway dm.putByte((byte)'\0'); } boolean gotResponse = simpleRequestResponseMessageIsolate(dm, DMessage.InRemoveExceptionBreakpoint, isolateId); if(gotResponse) { return true; } else throw new NoResponseException(getPreference(SessionManager.PREF_RESPONSE_TIMEOUT)); } @Override public void setLauncher(ILauncher launcher) { this.launcher = launcher; } }