13 Commits
7 changed files with 404 additions and 197 deletions
+80 -67
View File
@@ -1,7 +1,7 @@
Robust ABC (ActionScript Bytecode) [Dis-]Assembler
==================================================
[RABCDAsm][] is a collection of utilities including an ActionScript
[RABCDAsm][] is a collection of utilities including an ActionScript 3
assembler/disassembler, and a few tools to manipulate SWF files.
These are:
@@ -31,7 +31,7 @@ Motivation and goals
--------------------
This package was created due to lack of similar software out there.
Particularly, I needed an utility which would allow me to edit ActionScript
Particularly, I needed an utility which would allow me to edit ActionScript 3
bytecode with the following properties:
1. Speed. Less waiting means more productivity. `rabcasm` can assemble large
@@ -94,13 +94,17 @@ To disassemble one of the `.abc` files:
rabcdasm file0.abc
This will create a `file0` directory, which will contain `file0.main.asasm`
(the main program file) and a file per ActionScript class.
(the main program file), `file0.privatens.asasm` (private namespace alias
definitions) and a file per ActionScript class.
To assemble the `.asasm` files back, and update the SWF file:
rabcasm file0/file0.main.asasm
abcreplace file0.swf 0 file0/file0.main.abc
The second `abcreplace` argument represents the index of the ABC block in the
SWF file, and corresponds to the number in the filename created by `abcexport`.
Syntax
======
@@ -194,10 +198,11 @@ fields.
`code` blocks - always declared inline of their `body` block - are somewhat
different in syntax from other blocks - mostly in that they may contain labels.
Labels follow the most common syntax - a word followed by a `:` character.
Multiple instruction arguments are comma-separated. Instruction arguments'
types depend on the instruction - see the `OpcodeInfo` array in `abcfile.d`
for a reference.
Labels follow the most common syntax - a word followed by a `:` character,
optionally followed by a relative byte offset (in case of pointers inside
instructions). Multiple instruction arguments are comma-separated. Instruction
arguments' types depend on the instruction - see the `OpcodeInfo` array in
`abcfile.d` for a reference.
`try` blocks - always declared inline of their `body` block - represent an
"exception" (try/catch) block. They contain five mandatory fields: `from`,
@@ -233,8 +238,12 @@ namespace sets can be specified using `[]`.
Namespaces have the syntax *type* `(` *parameters* `)` . For types other than
`PrivateNamespace` there is only one parameter - a string. `PrivateNamespace`
namespaces have a second parameter - an integer to distinguish this private
namespace from others.
namespaces have a second parameter, a named alias for a particular private
namespace. Internally (the ABC file format), private namespaces are
distinguished by a numerical index - `rabcdasm` will attempt to give them
descriptive names based on their context. Aliases can be defined using the
`#privatens` directive. `rabcdasm` will create a separate file containing the
aliases (`file0.privatens.asasm`).
Strings have a syntax similar to C string literals. Strings start and end with
a `"`. Supported escape sequences (a backslash followed by a letter) are `\n`
@@ -277,6 +286,7 @@ Directives start with a `#`, followed by a word identifying the directive:
* `#set` *word* *string* - assigns the contents of the string to the
variable *word*.
* `#unset` *word* - deletes the variable *word*.
* `#privatens` defines a private namespace alias, as described above.
### Variables
@@ -304,20 +314,20 @@ be instantiated in two ways:
Here's an example of how to use the above features to create a macro which
logs a string literal and the contents of a register:
#set log "
findpropstrict QName(PackageNamespace(\"\"), \"log\")
pushstring $\"1\"
getlocal $2
callpropvoid QName(PackageNamespace(\"\"), \"log\"), 2
"
; ...
pushbyte 2
pushbyte 2
add_i
setlocal1
#call $"log"("two plus two equals", "1")
#set log "
findpropstrict QName(PackageNamespace(\"\"), \"log\")
pushstring $\"1\"
getlocal $2
callpropvoid QName(PackageNamespace(\"\"), \"log\"), 2
"
; ...
pushbyte 2
pushbyte 2
add_i
setlocal1
#call $"log"("two plus two equals", "1")
Highlighting
------------
@@ -337,19 +347,19 @@ instead of indexes, allowing easy manipulation without having to worry about
record order or constant pools. Conversion between various states is done as
follows:
file.abc
| ^
ABCReader | | ABCWriter
v |
ABCFile
| ^
ABCtoAS | | AStoABC
v |
ASProgram
| ^
Disassembler | | Assembler
v |
file.asasm
file.abc
| ^
------ ABCReader | | ABCWriter ----
/ v | \
/ ABCFile \
/ | ^ \
rabcdasm---------- ABCtoAS | | AStoABC --------rabcasm
\ v | /
\ ASProgram /
\ | ^ /
--- Disassembler | | Assembler ----
v |
file.asasm
`AStoABC` will rebuild the constant pools, in a manner similar to Adobe's
compilers (reverse-sorted by reference count). The exact order will almost
@@ -376,24 +386,30 @@ RABCDAsm users.
[Git]: http://git-scm.com/
[Mercurial]: http://mercurial.selenic.com/
2. The [Fiddler][] Web Debugging Proxy can be very useful for analysing
2. If you plan on making non-trivial changes to SWF files, you should install
the [debug Flash Player][]. This will allow you to see validation and
run-time error messages, instead of simply getting a blank rectangle.
[debug Flash Player]: http://www.adobe.com/support/flashplayer/downloads.html
3. The [Fiddler][] Web Debugging Proxy can be very useful for analysing
websites with SWF content. The following script fragment (which is to be
placed in the `OnBeforeResponse` function) will automatically save all SWF
files while preserving the directory structure.
if (oSession.oResponse.headers.ExistsAndContains("Content-Type",
"application/x-shockwave-flash")) {
// Set desired path here
var path:String = "C:\\Temp\\FiddlerCapture\\" +
oSession.host + oSession.PathAndQuery;
if (path.Contains('?'))
path = path.Substring(0, path.IndexOf('?'));
var dir:String = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
oSession.utilDecodeResponse();
oSession.SaveResponseBody(path);
}
if (oSession.oResponse.headers.ExistsAndContains("Content-Type",
"application/x-shockwave-flash")) {
// Set desired path here
var path:String = "C:\\Temp\\FiddlerCapture\\" +
oSession.host + oSession.PathAndQuery;
if (path.Contains('?'))
path = path.Substring(0, path.IndexOf('?'));
var dir:String = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
oSession.utilDecodeResponse();
oSession.SaveResponseBody(path);
}
Once you have edited a SWF file, you can use Fiddler's [AutoResponder][] to
replace the original file with your modified version.
@@ -404,28 +420,25 @@ RABCDAsm users.
Limitations
===========
1. Metadata is currently ignored. I haven't noticed any metadata blocks in any
SWF files I've disassembled.
* Metadata is currently ignored. I haven't noticed any metadata blocks in any
SWF files I've disassembled.
2. Private namespaces are currently represented by an automatically-assigned
integer. This causes problems when comparing disassemblies from two
versions of a file, since those numbers are prone to change when classes
are added or removed.
* Floating point numbers may not be disassembled with adequate precision.
3. `rabcasm` may create a broken file due to not ordering classes by ancestry.
* `rabcasm` may create a broken file due to not ordering classes by ancestry.
The problem originates from the fact that a class's ancestors (extended
class and implemented interfaces) are stored as multinames, and not as
class indices. (This makes sense, since classes may extend objects outside
the current ABC file.) Since `rabcasm` currently doesn't decode multinames,
it is unaware of the class dependencies, and may thus write the classes out
of order. This results in a file that, when opened, will fail to load with
an error message similar to:
The problem originates from the fact that a class's ancestors (extended
class and implemented interfaces) are stored as multinames, and not as
class indices. (This makes sense, since classes may extend objects outside
the current ABC file.) Since `rabcasm` currently doesn't decode multinames,
it is unaware of the class dependencies, and may thus write the classes out
of order. This results in a file that, when opened, will fail to load with
an error message similar to:
VerifyError: Error #1014: Class AncestorClassName could not be found.
`VerifyError: Error #1014: Class AncestorClassName could not be found.`
The simple work-around is to re-order the classes as they are declared in
the `.main.asasm` file, and place ancestors before descendants.
The simple work-around is to re-order the classes as they are declared in
the `.main.asasm` file, and place ancestors before descendants.
License
=======
+22 -16
View File
@@ -21,6 +21,7 @@ module abcexport;
import std.file;
import std.path;
import std.string;
import std.stdio;
import swffile;
void main(string[] args)
@@ -28,22 +29,27 @@ void main(string[] args)
if (args.length == 1)
throw new Exception("No file specified");
foreach (arg; args[1..$])
{
scope swf = SWFFile.read(cast(ubyte[])read(arg));
uint count;
foreach (ref tag; swf.tags)
if ((tag.type == TagType.DoABC || tag.type == TagType.DoABC2))
{
ubyte[] abc;
if (tag.type == TagType.DoABC)
abc = tag.data;
else
try
{
scope swf = SWFFile.read(cast(ubyte[])read(arg));
uint count = 0;
foreach (ref tag; swf.tags)
if ((tag.type == TagType.DoABC || tag.type == TagType.DoABC2))
{
auto p = tag.data.ptr+4; // skip flags
while (*p++) {} // skip name
abc = tag.data[p-tag.data.ptr..$];
ubyte[] abc;
if (tag.type == TagType.DoABC)
abc = tag.data;
else
{
auto p = tag.data.ptr+4; // skip flags
while (*p++) {} // skip name
abc = tag.data[p-tag.data.ptr..$];
}
write(getName(arg) ~ .toString(count++) ~ ".abc", abc);
}
write(getName(arg) ~ .toString(count++) ~ ".abc", abc);
}
}
if (count == 0)
throw new Exception("No DoABC tags found");
}
catch (Object o)
writefln("Error while processing %s: %s", arg, o);
}
+64 -47
View File
@@ -23,12 +23,7 @@ import std.string : format; // exception formatting
/**
* Implements a shallow representation of an .abc file.
* Loading and saving an .abc file using this class should produce
* output identical to the input - with one exception:
*
* exception_info's "to" field may point inside an instruction.
* This is apparently valid according to the implementation, however
* the exact offset inside the instruction is not preserved - instead,
* the field will point to the beginning of the next instruction.
* output identical to the input.
*/
class ABCFile
@@ -213,6 +208,20 @@ class ABCFile
TraitsInfo[] traits;
}
/// Destination for a jump or exception block boundary
struct Label
{
union
{
struct
{
uint index; /// instruction index
int offset; /// signed offset relative to said instruction
}
private int absoluteOffset; /// internal temporary value used during reading and writing
}
}
struct Instruction
{
Opcode opcode;
@@ -223,15 +232,15 @@ class ABCFile
ulong uintv;
uint index;
uint jumpTarget;
uint[] switchTargets;
Label jumpTarget;
Label[] switchTargets;
}
Argument[] arguments;
}
struct ExceptionInfo
{
uint from, to, target;
Label from, to, target;
uint excType;
uint varName;
}
@@ -1268,25 +1277,32 @@ private final class ABCReader
size_t len = readU30();
uint[] instructionAtOffset = new uint[len];
void offsetToIndex(ref uint x, bool relaxed = false)
void translateLabel(ref ABCFile.Label label)
{
if (x >= len)
int absoluteOffset = label.absoluteOffset;
int instructionOffset = absoluteOffset;
while (true)
{
//throw new Exception(format("Jump out of bounds (by %d bytes)", x - len));
// Unreachable OOB jumps seem to be "valid"
x = 0;
return;
}
if (relaxed)
while (instructionAtOffset[x] == uint.max)
if (instructionOffset >= cast(int)len)
{
x++;
if (x >= len)
throw new Exception("Relaxed jump inside last instruction");
label.index = r.instructions.length;
instructionOffset = len;
break;
}
x = instructionAtOffset[x];
if (x == uint.max)
throw new Exception("Jump inside instruction");
if (instructionOffset <= 0)
{
label.index = 0;
instructionOffset = 0;
break;
}
if (instructionAtOffset[instructionOffset] != uint.max)
{
label.index = instructionAtOffset[instructionOffset];
break;
}
instructionOffset--;
}
label.offset = absoluteOffset-instructionOffset;
}
{
@@ -1333,17 +1349,17 @@ private final class ABCReader
case OpcodeArgumentType.JumpTarget:
int delta = readS24();
instruction.arguments[i].jumpTarget = offset + delta;
instruction.arguments[i].jumpTarget.absoluteOffset = offset + delta;
break;
case OpcodeArgumentType.SwitchDefaultTarget:
instruction.arguments[i].jumpTarget = instructionOffset + readS24();
instruction.arguments[i].jumpTarget.absoluteOffset = instructionOffset + readS24();
break;
case OpcodeArgumentType.SwitchTargets:
instruction.arguments[i].switchTargets.length = readU30()+1;
foreach (ref off; instruction.arguments[i].switchTargets)
off = instructionOffset + readS24();
foreach (ref label; instruction.arguments[i].switchTargets)
label.absoluteOffset = instructionOffset + readS24();
break;
default:
@@ -1363,13 +1379,11 @@ private final class ABCReader
{
case OpcodeArgumentType.JumpTarget:
case OpcodeArgumentType.SwitchDefaultTarget:
pos = start + instructionOffsets[ii];
offsetToIndex(instruction.arguments[i].jumpTarget);
translateLabel(instruction.arguments[i].jumpTarget);
break;
case OpcodeArgumentType.SwitchTargets:
pos = start + instructionOffsets[ii];
foreach (ref x; instruction.arguments[i].switchTargets)
offsetToIndex(x);
translateLabel(x);
break;
default:
break;
@@ -1381,9 +1395,9 @@ private final class ABCReader
foreach (ref value; r.exceptions)
{
value = readExceptionInfo();
offsetToIndex(value.from);
offsetToIndex(value.to, true);
offsetToIndex(value.target);
translateLabel(value.from);
translateLabel(value.to);
translateLabel(value.target);
}
r.traits.length = readU30();
foreach (ref value; r.traits)
@@ -1394,9 +1408,9 @@ private final class ABCReader
ABCFile.ExceptionInfo readExceptionInfo()
{
ABCFile.ExceptionInfo r;
r.from = readU30();
r.to = readU30();
r.target = readU30();
r.from.absoluteOffset = readU30();
r.to.absoluteOffset = readU30();
r.target.absoluteOffset = readU30();
r.excType = readU30();
r.varName = readU30();
return r;
@@ -1731,7 +1745,9 @@ private final class ABCWriter
writeU30(v.initScopeDepth);
writeU30(v.maxScopeDepth);
uint[] instructionOffsets = new uint[v.instructions.length];
uint[] instructionOffsets = new uint[v.instructions.length+1];
uint resolveLabel(ref ABCFile.Label label) { return instructionOffsets[label.index]+label.offset; }
{
// we don't know the length before writing all the instructions - swap buffer with a temporary one
@@ -1740,7 +1756,7 @@ private final class ABCWriter
buf = new ubyte[1024];
pos = 0;
struct Fixup { uint target, pos, base; }
struct Fixup { ABCFile.Label target; uint pos, base; }
Fixup[] fixups;
foreach (ii, ref instruction; v.instructions)
@@ -1807,11 +1823,12 @@ private final class ABCWriter
}
buf.length = pos;
instructionOffsets[v.instructions.length] = pos;
foreach (ref fixup; fixups)
{
pos = fixup.pos;
writeS24(instructionOffsets[fixup.target]-fixup.base);
writeS24(resolveLabel(fixup.target)-fixup.base);
}
auto code = buf;
@@ -1825,9 +1842,9 @@ private final class ABCWriter
writeU30(v.exceptions.length);
foreach (ref value; v.exceptions)
{
value.from = instructionOffsets[value.from];
value.to = instructionOffsets[value.to];
value.target = instructionOffsets[value.target];
value.from.absoluteOffset = resolveLabel(value.from);
value.to.absoluteOffset = resolveLabel(value.to);
value.target.absoluteOffset = resolveLabel(value.target);
writeExceptionInfo(value);
}
writeU30(v.traits.length);
@@ -1837,9 +1854,9 @@ private final class ABCWriter
void writeExceptionInfo(ABCFile.ExceptionInfo v)
{
writeU30(v.from);
writeU30(v.to);
writeU30(v.target);
writeU30(v.from.absoluteOffset);
writeU30(v.to.absoluteOffset);
writeU30(v.target.absoluteOffset);
writeU30(v.excType);
writeU30(v.varName);
}
+1
View File
@@ -52,6 +52,7 @@
<word name="#get"/>
<word name="#include"/>
<word name="#mixin"/>
<word name="#privatens"/>
<word name="#set"/>
<word name="#unset"/>
</keywords>
+3 -3
View File
@@ -250,15 +250,15 @@ final class ASProgram
Class classv;
Method methodv;
uint jumpTarget;
uint[] switchTargets;
ABCFile.Label jumpTarget;
ABCFile.Label[] switchTargets;
}
Argument[] arguments;
}
struct Exception
{
uint from, to, target;
ABCFile.Label from, to, target;
Multiname excType;
Multiname varName;
}
+64 -31
View File
@@ -21,6 +21,7 @@ module assembler;
import std.file;
import std.string;
import std.conv;
import std.path;
import abcfile;
import asprogram;
@@ -46,16 +47,22 @@ final class Assembler
char* pos;
char* end;
string[] arguments;
string basePath;
static File load(string filename, string[] arguments = null)
{
return fromData(filename, cast(string)read(filename), arguments);
return fromFile(filename, cast(string)read(filename), arguments);
}
static File fromFile(string filename, string data, string[] arguments = null)
{
return fromData(filename, data, arguments, getDirName(filename));
}
static File fromData(string name, string data, string[] arguments = null)
static File fromData(string name, string data, string[] arguments = null, string basePath = null)
{
data ~= \0; data = data[0..$-1]; // hack to prevent readWord etc. from checking for end-of-file on every character
return File(name, data, data.ptr, data.ptr + data.length, arguments);
return File(name, data, data.ptr, data.ptr + data.length, arguments, basePath);
}
Position position()
@@ -79,8 +86,14 @@ final class Assembler
File[64] files;
int fileCount; /// recursion depth
string basePath;
string getBasePath()
{
foreach (ref file; files[0..fileCount])
if (file.basePath !is null)
return file.basePath;
return null;
}
string convertFilename(string filename)
{
if (filename.length == 0)
@@ -89,18 +102,9 @@ final class Assembler
foreach (ref c; filename)
if (c == '\\')
c = '/';
version(Windows)
{
if (filename.length > 2 && filename[1] == ':')
return filename;
}
if (filename[0] == '/')
return filename;
return basePath ~ filename;
return std.path.join(getBasePath, filename);
}
string[string] vars;
void skipWhitespace()
{
while (true)
@@ -126,6 +130,9 @@ final class Assembler
}
}
string[string] vars;
uint[string] privateNamespaces;
void handlePreprocessor()
{
skipChar(); // #
@@ -143,7 +150,7 @@ final class Assembler
break;
case "get":
auto filename = convertFilename(readString());
pushFile(File.fromData(filename, toStringLiteral(cast(string)read(filename))));
pushFile(File.fromFile(filename, toStringLiteral(cast(string)read(filename))));
break;
case "set":
vars[readWord()] = readString();
@@ -151,6 +158,10 @@ final class Assembler
case "unset":
vars.remove(readWord());
break;
case "privatens":
uint index = cast(uint)readUInt();
privateNamespaces[readString()] = index;
break;
default:
files[0].pos -= word.length;
throw new Exception("Unknown preprocessor declaration: " ~ word);
@@ -545,7 +556,11 @@ final class Assembler
if (n.kind == ASType.PrivateNamespace)
{
expectChar(',');
n.privateIndex = cast(uint)readUInt();
string name = readString();
auto pindex = name in privateNamespaces;
if (pindex is null)
throw new Exception("Unknown private namespace name");
n.privateIndex = *pindex;
}
expectChar(')');
return n;
@@ -915,6 +930,24 @@ final class Assembler
}
}
ABCFile.Label parseLabel(string label, uint[string] labels)
{
string name = label;
int offset = 0;
foreach (i, c; label)
if (c=='-' || c=='+')
{
name = label[0..i];
offset = .toInt(label[i..$]);
break;
}
auto lp = name in labels;
if (lp is null)
throw new Exception("Unknown label " ~ name);
return ABCFile.Label(*lp, offset);
}
ASProgram.Instruction[] readInstructions(ref uint[string] _labels)
{
ASProgram.Instruction[] instructions;
@@ -994,7 +1027,7 @@ final class Assembler
case OpcodeArgumentType.SwitchTargets:
string[] switchTargetLabels = readList!('[', ']', readWord, false)();
instruction.arguments[i].switchTargets = new uint[switchTargetLabels.length];
instruction.arguments[i].switchTargets.length = switchTargetLabels.length;
foreach (li, s; switchTargetLabels)
switchFixups ~= LocalFixup(files[0].position, instructions.length, i, s, li);
break;
@@ -1011,24 +1044,24 @@ final class Assembler
foreach (ref f; jumpFixups)
{
auto lp = f.name in labels;
if (lp is null)
try
instructions[f.ii].arguments[f.ai].jumpTarget = parseLabel(f.name, labels);
catch (Object o)
{
setFile(f.where.load);
throw new Exception("Unknown label " ~ f.name);
throw o;
}
instructions[f.ii].arguments[f.ai].jumpTarget = *lp;
}
foreach (ref f; switchFixups)
{
auto lp = f.name in labels;
if (lp is null)
try
instructions[f.ii].arguments[f.ai].switchTargets[f.si] = parseLabel(f.name, labels);
catch (Object o)
{
setFile(f.where.load);
throw new Exception("Unknown label " ~ f.name);
throw o;
}
instructions[f.ii].arguments[f.ai].switchTargets[f.si] = *lp;
}
foreach (ref f; localClassFixups)
@@ -1042,16 +1075,16 @@ final class Assembler
ASProgram.Exception readException(uint[string] labels)
{
uint readLabel()
ABCFile.Label readLabel()
{
auto word = readWord();
auto plabel = word in labels;
if (plabel is null)
try
return parseLabel(word, labels);
catch (Object o)
{
backpedal(word.length);
throw new Exception("Unknown label " ~ word);
throw o;
}
return *plabel;
}
ASProgram.Exception e;
+170 -33
View File
@@ -95,6 +95,9 @@ final class RefBuilder : ASTraitsVisitor
ASProgram.Class[string] classByName;
ASProgram.Method[string] methodByName;
string[uint] privateNamespaceNames;
uint[string] privateNamespaceByName;
ASProgram.Multiname[] context;
this(ASProgram as)
@@ -106,12 +109,23 @@ final class RefBuilder : ASTraitsVisitor
{
foreach (i, ref v; as.scripts)
addMethod(v.sinit, "script" ~ .toString(i) ~ "_sinit");
foreach (vclass; as.orphanClasses)
addClass(vclass, "orphan");
foreach (method; as.orphanMethods)
addMethod(method, "orphan");
super.run();
}
override void visitTrait(ref ASProgram.Trait trait)
{
context ~= trait.name;
auto m = trait.name;
if (m.kind != ASType.QName)
throw new Exception("Trait name is not a QName");
visitMultiname(m);
context ~= m;
switch (trait.kind)
{
case TraitKind.Class:
@@ -136,16 +150,96 @@ final class RefBuilder : ASTraitsVisitor
context = context[0..$-1];
}
string addPrivateNamespace(uint index, string bname)
{
string name = bname;
{
int n = 0;
uint* pindex;
while ((pindex = name in privateNamespaceByName) !is null && *pindex != index)
name = bname ~ .toString(++n);
}
auto pname = index in privateNamespaceNames;
if (pname)
{
if (*pname != name)
throw new Exception("Ambiguous private namespace: " ~ *pname ~ " and " ~ name);
}
else
{
privateNamespaceNames[index] = name;
privateNamespaceByName[name] = index;
}
return name;
}
void visitNamespace(ASProgram.Namespace ns)
{
if (ns.kind == ASType.PrivateNamespace && context.length>0 && context[0].vQName.ns.kind != ASType.PrivateNamespace)
addPrivateNamespace(ns.privateIndex, qNameToString(context[0]));
}
void visitNamespaceSet(ASProgram.Namespace[] nsSet)
{
foreach (ns; nsSet)
visitNamespace(ns);
}
void visitMultiname(ASProgram.Multiname m)
{
with (m)
switch (kind)
{
case ASType.QName:
case ASType.QNameA:
visitNamespace(vQName.ns);
break;
case ASType.Multiname:
case ASType.MultinameA:
visitNamespaceSet(vMultiname.nsSet);
break;
case ASType.MultinameL:
case ASType.MultinameLA:
visitNamespaceSet(vMultinameL.nsSet);
break;
case ASType.TypeName:
visitMultiname(vTypeName.name);
foreach (param; vTypeName.params)
visitMultiname(param);
break;
default:
break;
}
}
void visitMethodBody(ASProgram.MethodBody b)
{
foreach (ref instruction; b.instructions)
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
switch (type)
{
case OpcodeArgumentType.Namespace:
visitNamespace(instruction.arguments[i].namespacev);
break;
case OpcodeArgumentType.Multiname:
visitMultiname(instruction.arguments[i].multinamev);
break;
default:
break;
}
}
static string qNameToString(ASProgram.Multiname m)
{
assert(m.kind == ASType.QName);
return (m.vQName.ns.name.length ? m.vQName.ns.name ~ ":" : "") ~ m.vQName.name;
}
string contextToString(string field)
{
string[] strings = new string[context.length + (field ? 1 : 0)];
foreach (i, m; context)
{
// should this check ever fail, it's easy to fix it - just build any unique-ish string from the context
if (m.kind != ASType.QName)
throw new Exception("Trait name is not a QName");
strings[i] = (m.vQName.ns.name.length ? m.vQName.ns.name ~ "." : "") ~ m.vQName.name;
}
strings[i] = qNameToString(m);
if (field)
strings[$-1] = field;
string s = join(strings, "/");
@@ -167,9 +261,9 @@ final class RefBuilder : ASTraitsVisitor
return uniqueName;
}
void addClass(ASProgram.Class vclass)
void addClass(ASProgram.Class vclass, string field = null)
{
addObject(vclass, classByName, string.init);
addObject(vclass, classByName, field);
addMethod(vclass.cinit, "cinit");
addMethod(vclass.instance.iinit, "iinit");
}
@@ -177,6 +271,8 @@ final class RefBuilder : ASTraitsVisitor
void addMethod(ASProgram.Method method, string field = null)
{
addObject(method, methodByName, field);
if (method.vbody)
visitMethodBody(method.vbody);
}
string getObjectName(T)(T obj, ref T[string] objByName)
@@ -197,6 +293,16 @@ final class RefBuilder : ASTraitsVisitor
{
return getObjectName(method, methodByName);
}
string getPrivateNamespaceName(uint index)
{
auto pname = index in privateNamespaceNames;
if (pname)
return *pname;
else
//throw new Exception("Nameless private namespace: " ~ .toString(index));
return addPrivateNamespace(index, "OrphanPrivateNamespace");
}
}
final class Disassembler
@@ -221,6 +327,10 @@ final class Disassembler
StringBuilder sb = new StringBuilder(name ~ "/" ~ name ~ ".main.asasm");
sb ~= "#include ";
dumpString(sb, name ~ ".privatens.asasm");
sb.newLine();
sb ~= "program";
sb.indent++; sb.newLine();
@@ -272,6 +382,17 @@ final class Disassembler
sb ~= "end ; program"; sb.newLine();
sb.save();
// now dump the private namespace indices
sb = new StringBuilder(name ~ "/" ~ name ~ ".privatens.asasm");
uint[] indices = refs.privateNamespaceNames.keys.sort;
foreach (index; indices)
{
sb ~= "#privatens " ~ .toString(index) ~ " ";
dumpString(sb, refs.privateNamespaceNames[index]);
sb.newLine();
}
sb.save();
}
void dumpInt(StringBuilder sb, long v)
@@ -345,7 +466,7 @@ final class Disassembler
if (kind == ASType.PrivateNamespace)
{
sb ~= ", ";
dumpUInt(sb, privateIndex);
dumpString(sb, refs.getPrivateNamespaceName(privateIndex));
}
sb ~= ')';
}
@@ -600,10 +721,10 @@ final class Disassembler
{
string filename = refid.dup;
foreach (ref c; filename)
if (c == '.')
if (c == '.' || c == ':')
c = '/';
else
if (c == '\\' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|')
if (c == '\\' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|')
c = '_';
version (Windows)
@@ -701,6 +822,18 @@ final class Disassembler
sb.newLine();
}
void dumpLabel(StringBuilder sb, ref ABCFile.Label label)
{
sb ~= 'L';
sb ~= .toString(label.index);
if (label.offset != 0)
{
if (label.offset > 0)
sb ~= '+';
sb ~= .toString(label.offset);
}
}
void dumpMethodBody(StringBuilder sb, ASProgram.MethodBody mbody)
{
sb ~= "body";
@@ -712,22 +845,22 @@ final class Disassembler
sb ~= "code";
sb.newLine();
bool[] labels = new bool[mbody.instructions.length];
bool[] labels = new bool[mbody.instructions.length+1];
// reserve exception labels
foreach (ref e; mbody.exceptions)
labels[e.from] = labels[e.to] = labels[e.target] = true;
labels[e.from.index] = labels[e.to.index] = labels[e.target.index] = true;
dumpInstructions(sb, mbody.instructions, labels);
sb ~= "end ; code";
sb.newLine();
foreach (ref e; mbody.exceptions)
{
sb ~= "try from L";
sb ~= .toString(e.from);
sb ~= " to L";
sb ~= .toString(e.to);
sb ~= " target L";
sb ~= .toString(e.target);
sb ~= "try from ";
dumpLabel(sb, e.from);
sb ~= " to ";
dumpLabel(sb, e.to);
sb ~= " target ";
dumpLabel(sb, e.target);
sb ~= " type ";
dumpMultiname(sb, e.excType);
sb ~= " name ";
@@ -748,22 +881,18 @@ final class Disassembler
{
case OpcodeArgumentType.JumpTarget:
case OpcodeArgumentType.SwitchDefaultTarget:
labels[instruction.arguments[i].jumpTarget] = true;
labels[instruction.arguments[i].jumpTarget.index] = true;
break;
case OpcodeArgumentType.SwitchTargets:
foreach (ref x; instruction.arguments[i].switchTargets)
labels[x] = true;
foreach (ref label; instruction.arguments[i].switchTargets)
labels[label.index] = true;
break;
default:
break;
}
bool extraNewLine = false;
foreach (ii, ref instruction; instructions)
void checkLabel(uint ii)
{
if (extraNewLine)
sb.newLine();
extraNewLine = newLineAfter[instruction.opcode];
if (labels[ii])
{
sb.noIndent();
@@ -772,6 +901,15 @@ final class Disassembler
sb ~= ':';
sb.newLine();
}
}
bool extraNewLine = false;
foreach (ii, ref instruction; instructions)
{
if (extraNewLine)
sb.newLine();
extraNewLine = newLineAfter[instruction.opcode];
checkLabel(ii);
sb ~= opcodeInfo[instruction.opcode].name;
auto argTypes = opcodeInfo[instruction.opcode].argumentTypes;
@@ -823,8 +961,7 @@ final class Disassembler
case OpcodeArgumentType.JumpTarget:
case OpcodeArgumentType.SwitchDefaultTarget:
sb ~= 'L';
sb ~= .toString(instruction.arguments[i].jumpTarget);
dumpLabel(sb, instruction.arguments[i].jumpTarget);
break;
case OpcodeArgumentType.SwitchTargets:
@@ -832,8 +969,7 @@ final class Disassembler
auto targets = instruction.arguments[i].switchTargets;
foreach (ti, t; targets)
{
sb ~= 'L';
sb ~= .toString(t);
dumpLabel(sb, t);
if (ti < targets.length-1)
sb ~= ", ";
}
@@ -849,6 +985,7 @@ final class Disassembler
}
sb.newLine();
}
checkLabel(instructions.length);
sb.indent--;
}
}