blob: c8be56d8226f3fe6ba3cfdca6888b8d7c55248a3 [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.swf;
import flash.swf.debug.DebugModule;
import flash.swf.debug.LineRecord;
import flash.swf.debug.RegisterRecord;
import flash.swf.types.FlashUUID;
import java.io.IOException;
import java.io.OutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Iterator;
/**
* Encoder for writing out SWD files in a canonical order.
* <p>
* The MXML compiler uses DebugEncoder to output a SWD file
* containing debugging information for use by fdb.
* <p>
* The compiler incrementally feeds debugging data to this class
* by calling its offset() method. The debugging data is stored
* in DebugScript, DebugOffset, and DebugBreakpoint objects
* so that it can be massaged and sorted into a canonical order
* appropriate for fdb before being serialized as a SWD.
* <p>
* The organization of a canonical Royale SWD is as follows:
* <p>
* The kDebugID tag follows immediately after the SWD header.
* <p>
* The kDebugScript tags appear in the order in which fdb
* wants to display them:<p>
* - first the main MXML file for the application,
* with its "bitmap" field set to 1;<br>
* - then other authored files in alphabetical order, with bitmap=2;<br>
* - then framework files in alphabetical order, with bitmap=3;<br>
* - then pairs of synthetic files for the various components
* in alphabetical order, with bitmap=4;<br>
* - finally the synthetic "frame 1 actions" of the application,
* with bitmap=5.<br>
* <p>
* Alphabetical order refers to the short fdb display name
* ("Button.as"), not the complete module name
* ("mx.controls.Button: C:\Royale\...\mx\controls\Button.as").
* <p>
* The synthetic modules get renamed to &lt;MyComponent.1&gt;,
* &lt;MyComponent.2&gt;, and &lt;main&gt;.
* <p>
* After this sorting by name, the script id numbers are renumbered
* to be sequential, starting with 1.
* <p>
* Immediately after each kDebugScript tag come the kDebugOffset tags
* that reference that kDebugScript, ordered by line number and
* subordered by byte offset.
* <p>
* Royale emits no kDebugBreakpoint tags in its SWDs, but if it did
* they would come last, ordered by offset.
*
* @author Edwin Smith
* @author Gordon Smith
*/
public class DebugEncoder implements DebugHandler
{
/*
* Storage for the information that DebugEncoder
* will encode into the header of the SWD file.
* This is set when a client of DebugEncoder calls header().
*/
private int version;
/*
* Storage for the information that DebugEncoder
* will encode into a kDebugID tag in the SWD file.
* This is set when a client of DebugEncoder calls uuid() or updateUUID().
*/
private byte[] debugID;
/*
* A collection of DebugScript objects to be encoded into kDebugScript
* tags in the SWD file. This collection is built by repeated calls
* to offset() by clients of DebugEncoder. (Clients can also call module()
* directly, but the MXML compiler only calls offset().)
*/
private List<DebugScript> debugScripts;
/*
* Number of bytes written by last call to writeTo.
*/
private int bytesWritten = 0;
/*
* DebugScript objects are used only by this DebugEncoder class.
* Each DebugScript object contains the information that DebugEncoder
* will encode into one kDebugScript tag in the SWD file.
*/
private class DebugScript implements Comparable<DebugScript>
{
int bitmap;
String name;
String text;
/*
* A collection of DebugOffset objects to be encoded into kDebugOffset
* tags in the SWD file. This collection is built by repeated calls
* to offset() by clients of DebugEncoder. Since each DebugOffset
* is associated with one DebugScript, they are attached to their
* corresponding DebugScript rather than directly to the DebugEncoder.
*/
List<DebugOffset> debugOffsets;
String comparableName;
DebugScript(String name, String text)
{
this.bitmap = 0;
this.name = name;
this.text = text;
this.debugOffsets = new ArrayList<DebugOffset>();
}
/**
* Implement Comparable interface for sorting DebugScripts
*/
public int compareTo(DebugScript other)
{
return comparableName.compareTo(other.comparableName);
}
}
/*
* DebugOffset objects are used only by this DebugEncoder class.
* Each DebugOffset object stores the information that DebugEncoder
* will encode into one kDebugOffset tag in the SWD file.
* They are created by repeated calls to offset() by a client
* of DebugEncoder.
*/
private class DebugOffset implements Comparable<DebugOffset>
{
DebugOffset(int lineNumber, int byteOffset)
{
this.lineNumber = lineNumber;
this.byteOffset = byteOffset;
}
int lineNumber;
int byteOffset;
/**
* Implement Comparable interface for sorting DebugOffsets.
*/
public int compareTo(DebugOffset other)
{
long a = (((long)lineNumber) << 32) | byteOffset;
long b = (((long)other.lineNumber) << 32) | other.byteOffset;
if (a < b)
return -1;
else if (a > b)
return 1;
else
return 0;
}
}
/*
* A name -> DebugScript map used to quickly determine whether we
* have already created a DebugScript with a particular name.
* Note: We can't use the id in the DebugModule passed to offset()
* for this purpose, because it isn't unique! All the MXML files
* for the application and its components come in with id 0!
*/
private HashMap<String, DebugScript> debugScriptsByName;
/*
* A collection of DebugBreakpoint objects to be encoded into
* kDebugBreakpoint tags in the SWD file. This collection is built
* by repeated calls to breakpoint() by clients of DebugEncoder.
*/
private List<DebugBreakpoint> debugBreakpoints;
/*
* DebugOffset objects are used only by this DebugEncoder class.
* Each DebugBreakpoint object stores the information that DebugEncoder
* will encode into one kDebugBreakpoint tag in the SWD file.
*/
private class DebugBreakpoint implements Comparable<DebugBreakpoint>
{
DebugBreakpoint(int offset)
{
this.offset = offset;
}
int offset;
/**
* Implement Comparable interface for sorting DebugBreakpoints.
*/
public int compareTo(DebugBreakpoint other)
{
return new Integer(offset).compareTo(other.offset);
}
}
/*
* A collection of DebugRegister objects to be encoded into
* kDebugRegister tags in the SWD file. This collection is built
* by repeated calls to registers() by clients of DebugEncoder.
*/
private List<DebugRegisters> debugRegisters;
/*
* DebugRegisters objects are used only by this DebugEncoder class.
* Each DebugRegisters object stores the information that DebugEncoder
* will encode into one kDebugRegisters tag in the SWD file.
*/
private class DebugRegisters implements Comparable<DebugRegisters>
{
DebugRegisters(int offset, RegisterRecord r)
{
this.offset = offset;
this.registerNumbers = r.registerNumbers;
this.variableNames = r.variableNames;
}
int offset;
int[] registerNumbers;
String[] variableNames;
/**
* Implement Comparable interface for sorting DebugRegisters.
*/
public int compareTo(DebugRegisters other)
{
return new Integer(offset).compareTo(other.offset);
}
}
/*
* The MXML compiler sets this by calling setMainDebugScript()
* in order to specify which DebugScript should be first in the SWD,
* with id 1. fdb can then make this script the initial current script
* for a debugging session.
*/
private String mainDebugScriptName;
/*
* A public property of DebugEncoder used by clients to adjust
* the bytecode offsets for subsequent calls to offset().
* (From relative-to-beginning-of-byte-code-for-one-module
* to relative-to-beginning-of-SWD-file?)
*/
public int adjust;
public DebugEncoder()
{
debugScripts = new ArrayList<DebugScript>();
debugScriptsByName = new HashMap<String, DebugScript>();
debugBreakpoints = new ArrayList<DebugBreakpoint>();
debugRegisters = new ArrayList<DebugRegisters>();
}
public void header(int version)
{
this.version = version;
}
public void uuid(FlashUUID uuid)
{
debugID = uuid.bytes;
}
void updateUUID(byte[] uuid)
{
debugID = uuid;
}
public void offset(int offset, LineRecord lr)
{
//System.out.print(lr.module.id + " " + lr.module.name + " " + lr.lineno + " " + offset + "\n");
// NOTE: Each DebugModule coming in to this method via the
// LineRecord doesn't have a unique id!
// In particular, lr.module.id is 0 for all MXML files.
// And the others are generated by a random number generator
// that could conceivably repeat.
// Therefore there is no point at even looking at the id.
// Module name strings arrive here in various formats:
// An MXML file (application or component) is a full path like
// "C:\Royale\flash\experience\royale\apps\dev.war\checkinTest\checkinTest.mxml".
// An ActionScript component or a framework package combines
// a package name with a full path, as in
// "custom.as.myBox: C:\Royale\flash\experience\royale\apps\dev.war\checkinTest\custom\as\myBox.as"
// or
// "mx.core.UIComponent: C:\Royale\flash\experience\royale\apps\dev.war\WEB-INF\flex\frameworks\mx\core\UIComponent.as".
// A framework ActionScript file is a package an
// Various autogenerated modules look like this:
// "synthetic: checkinTest";
// "synthetic: Object.registerClass() for checkinTest";
// "synthetic: main frame 1 actions".
// #include files may have non-canonical full paths like
// "C:\Royale\flash\experience\royale\apps\dev.war\WEB-INF\flex\frameworks\mx\core\..\core\ComponentVersion.as"
// and must be canonicalized to
// "C:\Royale\flash\experience\royale\apps\dev.war\WEB-INF\flex\frameworks\mx\core\ComponentVersion.as"
// so that they don't show up multiple times in an fdb file listing.
/* C: The debug module name conversion is now centralized in DebugDecoder.adjustModuleName().
if (lr.module.name.indexOf(": ") < 0)
{
lr.module.name = FileUtils.canonicalPath(lr.module.name);
//System.out.print("*** " + lr.module.name + "\n");
}
*/
// Don't bother to record corrupted modules
if (lr.module.corrupt)
return;
// If we haven't already created a DebugScript for the
// module referenced by the specified LineRecord, do so.
String name = lr.module.name;
if (!debugScriptsByName.containsKey(name))
{
module(lr.module);
}
// Get the DebugScript for this script.
DebugScript script = debugScriptsByName.get(name);
// Create a DebugOffset for the specified lineNumber/byteOffset pair.
DebugOffset debugOffset =
new DebugOffset(lr.lineno, offset + adjust);
// Attach the DebugOffset to the DebugScript it is associated with.
script.debugOffsets.add(debugOffset);
}
public void module(DebugModule m)
{
if (m.corrupt)
return;
DebugScript script = new DebugScript(m.name, m.text);
debugScripts.add(script);
debugScriptsByName.put(script.name, script);
}
public void breakpoint(int offset)
{
debugBreakpoints.add(new DebugBreakpoint(offset));
}
public void registers(int offset, RegisterRecord r)
{
// Create a DebugRegister for the specified registers/byteOffset pair.
DebugRegisters debug = new DebugRegisters(offset + adjust, r);
debugRegisters.add(debug);
}
public void setMainDebugScript(String path)
{
mainDebugScriptName = path;
}
private static String generateShortName(String name)
{
String s = name;
/* do we have a file name? */
int dotAt = name.lastIndexOf('.');
if (dotAt != -1)
{
/* yes let's strip the directory off */
int lastSlashAt = name.lastIndexOf(File.separatorChar, dotAt);
if (lastSlashAt == -1 && File.separatorChar == '\\')
lastSlashAt = name.lastIndexOf('/', dotAt);
s = name.substring(lastSlashAt+1);
}
else
{
/* not a file name ... */
s = name;
}
return s.trim();
}
private void fixNamesAndBitmaps()
{
String synthetic = "synthetic: ";
String actions = "Actions for ";
Iterator<DebugScript> debugScriptIter = debugScripts.iterator();
while (debugScriptIter.hasNext())
{
DebugScript debugScript = debugScriptIter.next();
if (isFrameworkClass(debugScript.name))
{
// bitmap = 3 => Framework file
debugScript.bitmap = 3;
}
else if (debugScript.name.startsWith(synthetic))
{
// bitmap = 4 => Other per-component synthetic files
// produced by MXML compiler
debugScript.bitmap = 4;
String lookFor = "synthetic: Object.registerClass() for ";
if (debugScript.name.startsWith(lookFor))
{
String componentName = debugScript.name.substring(lookFor.length());
debugScript.name = "<" + componentName + ".2>";
}
else
{
// R: should really check for a collision here...
String componentName = debugScript.name.substring(synthetic.length());
debugScript.name = "<" + componentName + ".1>";
}
}
else if (debugScript.name.startsWith(actions))
{
// bitmap = 5 => Actions ...
debugScript.bitmap = 5;
}
else if (debugScript.name.equals(mainDebugScriptName))
{
// bitmap = 1 => Main MXML file for application
debugScript.bitmap = 1;
}
else
{
// bitmap = 2 => Other file, presumably an MXML or AS file
// written by the application author
debugScript.name = DebugDecoder.adjustModuleName(debugScript.name);
debugScript.bitmap = 2;
}
// Set the comparableName field of each DebugScript
// to the concatenation of the bitmap and the "short name" that fdb uses.
// This will ensure that DebugScripts are sorted alphabetically
// within each "bitmap" category.
debugScript.comparableName = (new Integer(debugScript.bitmap)).toString() +
generateShortName(debugScript.name);
// System.out.print(debugScript.comparableName + " " + debugScript.name);
}
}
private void encodeSwdData(SwfEncoder buffer)
{
// Encode the header.
buffer.write32(('F') | ('W' << 8) | ('D' << 16) | (version << 24));
// Encode one kDebugID tag.
buffer.write32(kDebugID);
buffer.write(debugID);
// Encode the kDebugScript and kDebugOffset tags.
// The kDebugScript tags are in module number order (1,2,3,...).
// After each one of these are the associated kDebugOffset tags
// for that module number, in ascending order
// by line number and byte offset.
Collections.sort(debugScripts);
int id = 0;
Iterator<DebugScript> debugScriptIter = debugScripts.iterator();
while (debugScriptIter.hasNext())
{
DebugScript debugScript = debugScriptIter.next();
id++;
buffer.write32(kDebugScript);
buffer.write32(id);
buffer.write32(debugScript.bitmap);
buffer.writeString(debugScript.name);
buffer.writeString(debugScript.text);
Collections.sort(debugScript.debugOffsets);
Iterator<DebugOffset> debugOffsetIter = debugScript.debugOffsets.iterator();
while (debugOffsetIter.hasNext())
{
DebugOffset debugOffset = debugOffsetIter.next();
buffer.write32(kDebugOffset);
buffer.write32(id);
buffer.write32(debugOffset.lineNumber);
buffer.write32(debugOffset.byteOffset);
}
}
// Encode the kDebugRegister tags
Collections.sort(debugRegisters);
Iterator<DebugRegisters> itr = debugRegisters.iterator();
while (itr.hasNext())
{
DebugRegisters debug = itr.next();
int size = debug.registerNumbers.length;
buffer.write32(kDebugRegisters);
buffer.write32(debug.offset);
buffer.writeUI8(size);
for(int i=0; i<debug.registerNumbers.length; i++)
{
buffer.writeUI8(debug.registerNumbers[i]);
buffer.writeString(debug.variableNames[i]);
}
}
// Encode the kDebugBreakpoint tags
Collections.sort(debugBreakpoints);
Iterator<DebugBreakpoint> debugBreakpointIterator = debugBreakpoints.iterator();
while (debugBreakpointIterator.hasNext())
{
DebugBreakpoint debugBreakpoint =
debugBreakpointIterator.next();
buffer.write32(kDebugBreakpoint);
buffer.write32(debugBreakpoint.offset);
}
}
public void writeTo(OutputStream out) throws IOException
{
SwfEncoder buffer = new SwfEncoder(version);
fixNamesAndBitmaps();
encodeSwdData(buffer);
buffer.writeTo(out);
bytesWritten = buffer.getBytesWritten();
}
public int getBytesWritten() {
return bytesWritten;
}
public void error(String msg)
{
}
/**
* C: The SWD generation for fdb is... flaky, especially the way to figure out
* the bitmap category based on debug module names. DebugModule and DebugScript
* must be set with a flag indicating whether they are classes, frame actions,
* etc, etc, etc.
*
* R: I don't particularly like it either and would prefer it if this stuff
* lived on the fdb side, not in here.
*/
private boolean isFrameworkClass(String name)
{
boolean isIt = ( name.startsWith("mx.") && name.indexOf(":") != -1 && name.endsWith(".as") )
|| ( name.indexOf("/mx/") > -1 );
return isIt;
}
}