Files
RABCDAsm/disassembler.d
Aaron Hill a2d18a1a72 Get RABCDAsm compiling with latest dlang
Both `opCatAssign` and `destroy` produce deprecation errors
as of the latest dlang (dmd 2.100.0). This commit changes them
both to use the suggested replacements, making RABCDAsm compile
with the latest dlang.
2022-06-15 20:14:47 -05:00

1846 lines
43 KiB
D

/*
* Copyright 2010, 2011, 2012, 2013, 2014, 2015 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RABCDAsm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RABCDAsm. If not, see <http://www.gnu.org/licenses/>.
*/
module disassembler;
import std.algorithm;
import std.array;
import std.conv;
import std.digest.md;
import std.exception;
import std.file;
import std.format;
import std.path;
import std.stdio;
import std.string;
import abcfile;
import asprogram;
import autodata;
import common;
alias std.array.join join;
final class StringBuilder
{
enum BUF_SIZE = 256*1024;
static char[] buf;
static size_t pos;
string filename;
File file;
this(string filename)
{
this.filename = filename;
if (exists(longPath(filename)))
throw new Exception(filename ~ " exists");
string[] dirSegments = split(filename, "/");
for (int l=0; l<dirSegments.length-1; l++)
{
auto subdir = join(dirSegments[0..l+1], "/");
if (subdir.length && !exists(longPath(subdir)))
mkdir(longPath(subdir));
}
file = openFile(filename, "wb");
assert(!pos, "Opening new file with unflushed buffer");
}
static this()
{
buf = new char[BUF_SIZE];
}
void put(in char[] s)
{
checkIndent();
auto end = pos + s.length;
if (end > buf.length)
{
flush();
end = s.length;
while (end > buf.length)
buf.length = buf.length*2;
}
buf[pos..end] = s[];
pos = end;
}
void put(char c)
{
if (pos == buf.length) // speed hack: no indent check
flush();
buf[pos++] = c;
}
void opOpAssign(string op : "~", V)(V s) {
put(s);
}
alias put opCatAssign;
void write(T)(T v)
{
checkIndent();
formattedWrite(this, "%s", v);
}
void flush()
{
if (pos)
{
file.rawWrite(buf[0..pos]);
pos = 0;
}
}
void save()
{
flush();
file.close();
}
int indent;
bool indented;
string linePrefix;
void newLine()
{
this ~= '\n';
indented = false;
}
void noIndent()
{
indented = true;
}
void checkIndent()
{
if (!indented)
{
for (int i=0; i<indent; i++)
this ~= ' ';
indented = true;
if (linePrefix)
this ~= linePrefix;
}
}
}
final class RefBuilder : ASTraitsVisitor
{
bool[uint][string][ASType.Max] homonyms;
debug bool homonymsBuilt;
bool hasHomonyms(ASProgram.Namespace ns)
{
debug assert(homonymsBuilt);
auto pnsHomonyms = ns.name in homonyms[ns.kind];
auto nsHomonyms = pnsHomonyms ? *pnsHomonyms : null;
return nsHomonyms.length > 1;
}
void addHomonym(ASProgram.Namespace ns)
{
debug assert(!homonymsBuilt);
homonyms[ns.kind][ns.name][ns.id] = true;
}
/// Represents a link in a "context chain", which represents the context in which an object is encountered.
/// The common root of contexts is used to build ref strings, namespace labels, file paths etc.
/// Expansion is recursive and delayed until decompilation (an object may be encountered inside a private namespace,
/// all contexts of which are unknown until the whole program is scanned).
struct ContextItem
{
enum Type
{
Multiname, /// An ASProgram.Multiname (may be a private namespace, etc.)
String, /// Fixed string
Group, /// Multiple ContextItem[]s (which are expanded and the common root is taken as the result)
}
Type type;
union
{
ASProgram.Multiname multiname;
struct
{
string str;
bool filenameSuffix;
}
struct
{
ContextItem[] group;
string groupFallback;
}
}
struct Segment
{
char delim;
string str;
}
ContextItem[] reduceGroup(RefBuilder refs)
{
assert(type==Type.Group);
ContextItem[][] contexts;
foreach (context; group)
contexts ~= ContextItem.expand(refs, [context]);
ContextItem[] context;
if (contexts.length)
context = reduce!contextRoot(contexts);
if (!context.length)
context = /*null*/[ContextItem(groupFallback)];
return context;
}
Segment[] toSegments(RefBuilder refs, bool filename)
out(result)
{
debug(CONTEXTS) std.stdio.writefln("Segmented:\n\t%s\nto\n\t%s\n", this, result);
}
body
{
final switch(type)
{
case Type.Multiname:
{
assert(multiname.kind == ASType.QName);
auto ns = multiname.vQName.ns;
auto nsName = ns.name;
// if (refs.hasHomonyms(ns))
// nsName ~= '#' ~ to!string(ns.id);
if (nsName.length)
if (multiname.vQName.name.length)
return [Segment('/', nsName), Segment(filename ? '/' : ':', multiname.vQName.name)];
else
return [Segment('/', nsName)];
else
if (multiname.vQName.name.length)
return [Segment('/', multiname.vQName.name)];
else
assert(0);
}
case Type.String:
return [Segment((filename && filenameSuffix) ? '.' : '/', str)];
case Type.Group:
{
Segment[] segments;
foreach (context; reduceGroup(refs))
segments ~= context.toSegments(refs, filename);
return segments;
}
}
}
static ContextItem[] expand(RefBuilder refs, ContextItem[] context)
{
ContextItem[] newContext;
foreach (ref c; context)
{
auto cEx = c.expand(refs);
if (cEx)
newContext ~= cEx;
}
return newContext;
}
bool expanding;
/// Recursively expand contexts (lower to Type.String ContextItems)
ContextItem[] expand(RefBuilder refs)
in
{
debug(CONTEXTS) std.stdio.writefln("Expanding:\n\t%s\n", this);
}
out(result)
{
debug(CONTEXTS) std.stdio.writefln("Expanded:\n\t%s\nto\n\t%s\n", this, result);
}
body
{
if (expanding)
final switch (type)
{
case Type.String:
assert(0);
case Type.Multiname:
return (&this)[0..1];
case Type.Group:
return [ContextItem(groupFallback)];
}
assert(!expanding);
expanding = true;
scope(exit) expanding = false;
final switch (type)
{
case Type.Multiname:
switch (multiname.kind)
{
case ASType.QName:
{
auto ns = multiname.vQName.ns;
if (ns.kind == ASType.PrivateNamespace)
{
// auto pcontext = ns.id in refs.namespaces[ns.kind].contexts;
// if (pcontext is null)
// return (&this)[0..1];
// assert(pcontext);
auto context = refs.namespaces[ns.kind].getContext(refs, ns.id);
debug(CONTEXTS) std.stdio.writefln("Context of namespace %s is:\n\t%s\n", ns, context);
// auto expanded = expand(refs, context);
// if (expanded is null) return null;
return /*expanded*/context ~ (multiname.vQName.name.length ? [ContextItem(multiname.vQName.name)] : null); // hack
}
break;
}
case ASType.Multiname:
return multiname.vMultiname.name.length ? [ContextItem(multiname.vMultiname.name)] : null;
default:
debug
assert(false, text(multiname.kind));
else
break;
}
break;
case Type.String:
break;
case Type.Group:
return reduceGroup(refs);
}
return (&this)[0..1];
}
this(ASProgram.Multiname m)
{
this.type = Type.Multiname;
this.multiname = m;
}
this(string s, bool filenameSuffix = false)
{
this.type = Type.String;
this.str = s;
this.filenameSuffix = filenameSuffix;
}
this(ContextItem[] group, string groupFallback)
{
this.type = Type.Group;
this.group = group;
this.groupFallback = groupFallback;
}
mixin AutoCompare;
mixin AutoToString;
R processData(R, string prolog, string epilog, H)(ref H handler) const
{
mixin(prolog);
mixin(addAutoField("type"));
final switch (type)
{
case Type.Multiname:
mixin(addAutoField("multiname"));
break;
case Type.String:
mixin(addAutoField("str"));
mixin(addAutoField("filenameSuffix"));
break;
case Type.Group:
mixin(addAutoField("group"));
mixin(addAutoField("groupFallback"));
break;
}
mixin(epilog);
}
static bool similar(ref ContextItem i1, ref ContextItem i2)
{
if (i1.type != i2.type) return false;
final switch (i1.type)
{
case ContextItem.Type.String:
return i1.str == i2.str;
case ContextItem.Type.Multiname:
assert(i1.multiname.kind == ASType.QName && i2.multiname.kind == ASType.QName);
if (i1.multiname.vQName.name != i2.multiname.vQName.name) return false;
return nsSimilar(i1.multiname.vQName.ns, i2.multiname.vQName.ns);
case ContextItem.Type.Group:
return i1.group == i2.group;
}
}
// truncate=true -> return partial ContextItem
// truncate=false -> return null on partial match
static ContextItem[] combine(bool truncate)(ref ContextItem c1, ref ContextItem c2)
{
if (similar(c1, c2))
return [c1];
if (c1.type != ContextItem.Type.Multiname || c2.type != ContextItem.Type.Multiname)
return null;
if (c1.multiname.kind != ASType.QName || c2.multiname.kind != ASType.QName)
return null;
auto name1 = c1.multiname.vQName.name;
auto name2 = c2.multiname.vQName.name;
auto ns1 = c1.multiname.vQName.ns;
auto ns2 = c2.multiname.vQName.ns;
if (nsSimilar(ns1, ns2) && ns1.name.length && truncate)
{
auto m = new ASProgram.Multiname;
m.kind = ASType.QName;
m.vQName.ns = ns1;
return [ContextItem(m)];
}
if (name1 && !name2 && truncate)
{
swap(c1, c2);
swap(ns1, ns2);
swap(name1, name2);
}
if (!name1 && name2 && nsSimilar(ns1, ns2))
{
if (truncate)
{
auto m = new ASProgram.Multiname;
m.kind = ASType.QName;
m.vQName.ns = ns1;
return [ContextItem(m)];
}
else
return [c2];
}
if (ns1.name.length && ns2.name.length)
{
if (nsSimilar(ns1, ns2))
{
assert(name1 != name2); // handled by similar() check
static if (truncate)
assert(false); // handled above
else
{
if (name2.length)
return [c1, ContextItem(name2)];
else
return [c1];
}
}
if (ns1.name.length > ns2.name.length && truncate)
{
swap(c1, c2);
swap(ns1, ns2);
swap(name1, name2);
}
auto fullName1 = ns1.name ~ (name1 ? ':' ~ name1 : "");
auto fullName2 = ns2.name ~ (name2 ? ':' ~ name2 : "");
if (fullName2.startsWith(fullName1 ~ ":"))
return [truncate ? c1 : c2];
}
return null;
}
alias combine!true commonRoot;
alias combine!false deduplicate;
}
ContextItem[] context; // potential optimization: use array-based stack
void pushContext(T...)(T v) { context ~= ContextItem(v); }
void popContext() { context = context[0..$-1]; }
enum ContextPriority
{
declaration,
usage,
orphan,
max
}
struct ContextSet(T, bool ALLOW_DUPLICATES)
{
ContextItem[][T] contexts;
ContextItem[][][ContextPriority.max][T] contextSets;
debug bool contextsSealed;
string[T] names, filenames;
debug bool coagulated;
bool add(U)(U obj, ContextItem[] context, ContextPriority priority)
{
debug assert(!coagulated);
debug assert(!contextsSealed);
auto p = cast(T)obj;
auto pset = p in contextSets;
if (!pset)
{
contextSets[p] = contextSets[p].init;
contextSets[p][priority] ~= context.dup;
return true;
}
else
{
static bool rawEqual(T)(T[] arr1, T[] arr2) { return cast(ubyte[])arr1 == cast(ubyte[])arr2; }
if ((*pset)[priority].length==0 || !rawEqual((*pset)[priority][$-1], context)) // Optimization: don't add contexts identical to the last added
(*pset)[priority] ~= context.dup;
return false;
}
}
bool addIfNew(U)(U obj, ContextItem[] context, ContextPriority priority)
{
if (isAdded(obj))
return false;
else
return add(obj, context, priority);
}
void coagulate(RefBuilder refs)
{
debug assert(!coagulated);
int[string] collisionCounter;
T[string] first;
foreach (obj, set; contextSets)
if (obj !in contexts)
getContext(refs, obj);
foreach (obj; contexts.keys.sort())
{
auto context = contexts[obj];
auto bname = refs.contextToString(context, false);
auto bfilename = refs.contextToString(context, true);
auto pcounter = bname in collisionCounter;
int counter = pcounter ? *pcounter : 0;
if (counter==1)
{
auto firstObj = first[bname];
names[firstObj] ~= "#0";
filenames[firstObj] ~= "#0";
}
string suffix;
if (counter==0)
first[bname] = cast(T)obj;
else
suffix = '#' ~ to!string(counter);
names[obj] = bname ~ suffix;
filenames[obj] = bfilename ~ suffix;
collisionCounter[bname] = counter+1;
}
debug coagulated = true;
}
bool isAdded(U)(U obj) { return (cast(T)obj in contextSets) ? true : false; }
ContextItem[] getContext(U)(RefBuilder refs, U obj)
{
// debug assert(coagulated);
debug contextsSealed = true;
auto p = cast(T)obj;
auto pcontext = p in contexts;
if (pcontext)
return *pcontext;
ContextItem[][] set;
foreach (prioritySet; contextSets[p])
if (prioritySet)
set = prioritySet;
static if (ALLOW_DUPLICATES)
{
auto context = ContextItem.expand(refs, set[0]);
foreach (setContext; set[1..$])
context = contextRoot(context, ContextItem.expand(refs, setContext));
return contexts[p] = context;
}
else
{
if (set.length > 1)
return contexts[p] = [ContextItem("multireferenced")];
else
return contexts[p] = ContextItem.expand(refs, set[0]);
}
}
string getName(U)(U obj)
{
debug assert(coagulated);
auto pname = cast(T)obj in names;
// return pname?*pname:"##" ~ format(obj);
assert(pname, format("Unscanned object: ", obj));
return *pname;
}
static string[string] filenameMappings;
string getFilename(U)(U obj, string suffix)
{
debug assert(coagulated);
auto pname = cast(T)obj in filenames;
assert(pname, format("Unscanned object: ", obj));
auto filename = *pname;
string[] dirSegments = split(filename, "/");
for (int l=0; l<dirSegments.length; l++)
{
again:
string subpath = join(dirSegments[0..l+1], "/");
string subpathl = toLower(subpath);
string* canonicalp = subpathl in filenameMappings;
if (canonicalp && *canonicalp != subpath)
{
dirSegments[l] = dirSegments[l] ~ "_"; // not ~=
goto again;
}
filenameMappings[subpathl] = subpath;
}
filename = join(dirSegments, "/");
return filename ~ "." ~ suffix ~ ".asasm";
}
}
ContextSet!(uint, true)[ASType.Max] namespaces;
ContextSet!(void*, false) objects, scripts;
this(ASProgram as)
{
super(as);
}
bool[void*] orphans;
void addOrphan(T)(T obj) { orphans[cast(void*)obj] = true; }
bool isOrphan(T)(T obj) { return (cast(void*)obj in orphans) ? true : false; }
override void run()
{
foreach (i, vclass; as.orphanClasses)
addOrphan(vclass);
foreach (i, method; as.orphanMethods)
addOrphan(method);
super.run();
foreach (i, v; as.scripts)
{
ContextItem[] classContexts;
foreach (trait; v.traits)
{
if (trait.name.kind == ASType.QName && trait.name.vQName.ns.kind != ASType.PrivateNamespace)
classContexts ~= ContextItem(trait.name);
}
if (!classContexts.length)
foreach (trait; v.traits)
classContexts ~= ContextItem(trait.name);
context = [ContextItem(classContexts, "script_" ~ to!string(i))];
scripts.add(v, context, ContextPriority.declaration);
pushContext("init", true);
addMethod(v.sinit, ContextPriority.declaration);
context = null;
}
foreach (i, vclass; as.orphanClasses)
if (!objects.isAdded(vclass))
{
pushContext("orphan_class_" ~ to!string(i));
addClass(vclass, ContextPriority.orphan);
popContext();
}
foreach (i, method; as.orphanMethods)
if (!objects.isAdded(method))
{
pushContext("orphan_method_" ~ to!string(i));
addMethod(method, ContextPriority.orphan);
popContext();
}
scripts.coagulate(this);
// add namespaces referenced only at script level
foreach (v; as.scripts)
foreach (trait; v.traits)
if (trait.name.kind == ASType.QName)
namespaces[trait.name.vQName.ns.kind].addIfNew(trait.name.vQName.ns.id, scripts.getContext(this, v), ContextPriority.declaration);
foreach (id, b; possibleOrphanPrivateNamespaces)
if (!namespaces[ASType.PrivateNamespace].isAdded(id))
{
pushContext("orphan_namespace_" ~ to!string(id));
namespaces[ASType.PrivateNamespace].add(id, context, ContextPriority.orphan);
popContext();
}
debug homonymsBuilt = true;
foreach (ref namespace; namespaces)
namespace.coagulate(this);
objects.coagulate(this);
}
override void visitTrait(ref ASProgram.Trait trait)
{
auto m = trait.name;
// if (m.kind != ASType.QName)
// throw new Exception("Trait name is not a QName");
pushContext(m);
visitMultiname(m, ContextPriority.declaration);
switch (trait.kind)
{
case TraitKind.Slot:
case TraitKind.Const:
visitMultiname(trait.vSlot.typeName, ContextPriority.usage);
super.visitTrait(trait);
break;
case TraitKind.Class:
addClass(trait.vClass.vclass, ContextPriority.declaration);
pushContext("class", true);
visitTraits(trait.vClass.vclass.traits);
popContext();
pushContext("instance", true);
visitTraits(trait.vClass.vclass.instance.traits);
popContext();
break;
case TraitKind.Function:
addMethod(trait.vFunction.vfunction, ContextPriority.declaration);
super.visitTrait(trait);
break;
case TraitKind.Method:
addMethod(trait.vMethod.vmethod, ContextPriority.declaration);
super.visitTrait(trait);
break;
case TraitKind.Getter:
pushContext("getter");
addMethod(trait.vMethod.vmethod, ContextPriority.declaration);
popContext();
super.visitTrait(trait);
break;
case TraitKind.Setter:
pushContext("setter");
addMethod(trait.vMethod.vmethod, ContextPriority.declaration);
popContext();
super.visitTrait(trait);
break;
default:
super.visitTrait(trait);
break;
}
popContext();
}
static ContextItem[] contextRoot(ContextItem[] c1, ContextItem[] c2)
{
static bool uninteresting(ContextItem[] c)
{
// Do some validation while we're at it
foreach (cc; c)
assert(cc.type != ContextItem.Type.Group, "Groups should be expanded by now");
return
(c.length==1 && c[0].type==ContextItem.Type.String && c[0].str.startsWith("script_") && c[0].str.endsWith("_sinit")) ||
(c.length==1 && c[0].type==ContextItem.Type.String && c[0].str.startsWith("orphan_method_")) ||
false;
}
if (uninteresting(c1)) return c2;
if (uninteresting(c2)) return c1;
ContextItem[] c;
while (c.length<c1.length && c.length<c2.length)
{
auto root = ContextItem.commonRoot(c1[c.length], c2[c.length]);
assert(root.length <= 1);
if (root.length)
c ~= root;
else
break;
}
return c;
}
bool[uint] possibleOrphanPrivateNamespaces;
void visitNamespace(ASProgram.Namespace ns, ContextPriority priority)
{
if (ns is null) return;
// Add to homonyms
addHomonym(ns);
// Add to namespaces (for context)
assert(context.length > 0, "No context");
auto myPos = context.length;
foreach (i, ref item; context)
if (item.type == ContextItem.Type.Multiname && item.multiname.kind == ASType.QName && item.multiname.vQName.ns == ns)
{
myPos = i;
break;
}
if (ns.kind == ASType.PrivateNamespace && myPos == 0)
{
possibleOrphanPrivateNamespaces[ns.id] = true;
return;
}
auto myContext = context[0..myPos];
namespaces[ns.kind].add(ns.id, myContext, priority);
}
void visitNamespaceSet(ASProgram.Namespace[] nsSet, ContextPriority priority)
{
foreach (ns; nsSet)
visitNamespace(ns, priority);
}
void visitMultiname(ASProgram.Multiname m, ContextPriority priority)
{
if (m is null) return;
with (m)
switch (kind)
{
case ASType.QName:
case ASType.QNameA:
visitNamespace(vQName.ns, priority);
break;
case ASType.Multiname:
case ASType.MultinameA:
visitNamespaceSet(vMultiname.nsSet, priority);
break;
case ASType.MultinameL:
case ASType.MultinameLA:
visitNamespaceSet(vMultinameL.nsSet, priority);
break;
case ASType.TypeName:
visitMultiname(vTypeName.name, priority);
foreach (param; vTypeName.params)
visitMultiname(param, priority);
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, ContextPriority.usage);
break;
case OpcodeArgumentType.Multiname:
visitMultiname(instruction.arguments[i].multinamev, ContextPriority.usage);
break;
case OpcodeArgumentType.Class:
pushContext("inline_class");
if (isOrphan(instruction.arguments[i].classv))
addClass(instruction.arguments[i].classv, ContextPriority.usage);
popContext();
break;
case OpcodeArgumentType.Method:
pushContext("inline_method");
if (isOrphan(instruction.arguments[i].methodv))
addMethod(instruction.arguments[i].methodv, ContextPriority.usage);
popContext();
break;
default:
break;
}
}
string contextToString(ContextItem[] context, bool filename)
in
{
debug(CONTEXTS) std.stdio.writefln("Stringizing:\n\t%s\n", context);
}
out(result)
{
debug(CONTEXTS) std.stdio.writefln("Stringized:\n\t%s\nto\n\t%s\n------------------\n", context, result);
}
body
{
context = ContextItem.expand(this, context);
if (!context.length)
return null;
foreach_reverse (i; 0..context.length-1)
{
auto root = ContextItem.deduplicate(context[i], context[i+1]);
if (root.length)
context = context[0..i] ~ root ~ context[i+2..$];
}
ContextItem.Segment[] segments;
foreach (ci; context)
segments ~= ci.toSegments(this, filename);
string escape(string s)
{
if (!filename)
return s;
string result;
foreach (c; s)
if (c == '.' || c == ':')
result ~= '/';
else
if (c == '\\' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|' || c < 0x20 || c >= 0x7F || c == ' ' || c == '%')
result ~= format("%%%02X", c);
else
result ~= c;
auto pathSegments = result.split("/");
if (!pathSegments.length)
pathSegments = [""];
foreach (ref pathSegment; pathSegments)
{
if (pathSegment == "")
pathSegment = "%";
static const reservedNames = ["CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"];
auto pathSegmentU = pathSegment.toUpper();
foreach (reservedName; reservedNames)
if (pathSegmentU.startsWith(reservedName) && (pathSegmentU.length == reservedName.length || pathSegmentU[reservedName.length]=='.'))
{
pathSegment = "%" ~ pathSegment;
break;
}
if (pathSegment.length > 240)
pathSegment = assumeUnique(pathSegment[0..200] ~ '-' ~ toHexString(md5Of(pathSegment)));
}
return arrayJoin(pathSegments, "/");
}
string[] strings = new string[segments.length];
foreach (i, ref s; segments)
strings[i] = (i>0 ? cast(string)[s.delim] : null) ~ escape(s.str);
return arrayJoin(strings);
}
bool addObject(T)(T obj, ContextPriority priority) { return objects.add(obj, context, priority); }
void addClass(ASProgram.Class vclass, ContextPriority priority)
{
addObject(vclass, priority);
pushContext("class", true);
pushContext("init", true);
addMethod(vclass.cinit, ContextPriority.declaration);
popContext(); // init
popContext(); // class
pushContext("instance", true);
pushContext("init", true);
addMethod(vclass.instance.iinit, ContextPriority.declaration);
popContext(); // init
visitMultiname(vclass.instance.name, ContextPriority.declaration);
visitMultiname(vclass.instance.superName, ContextPriority.usage);
visitNamespace(vclass.instance.protectedNs, ContextPriority.declaration);
foreach (iface; vclass.instance.interfaces)
visitMultiname(iface, ContextPriority.usage);
popContext(); // instance
}
void addMethod(ASProgram.Method method, ContextPriority priority)
{
if (addObject(method, priority))
{
foreach (paramType; method.paramTypes)
visitMultiname(paramType, ContextPriority.usage);
visitMultiname(method.returnType, ContextPriority.usage);
if (method.vbody)
visitMethodBody(method.vbody);
}
}
}
final class Disassembler
{
ASProgram as;
string name, dir;
RefBuilder refs;
bool dumpRaw = true;
void newInclude(StringBuilder mainsb, string filename, void delegate(StringBuilder) callback, bool doInline = true)
{
if (doInline)
{
string base = dirName(mainsb.filename);
string full = dir ~ "/" ~ filename;
uint up = 0;
while (!full.startsWith(base))
base = dirName(base), up++;
string rel = replicate("../", up) ~ full[base.length+1..$];
mainsb.flush();
StringBuilder sb = new StringBuilder(full);
callback(sb);
sb.save();
mainsb ~= "#include ";
dumpString(mainsb, rel);
mainsb.newLine();
}
else
callback(mainsb);
}
this(ASProgram as, string dir, string name)
{
this.as = as;
this.name = name;
this.dir = dir;
}
void disassemble()
{
refs = new RefBuilder(as);
refs.run();
StringBuilder sb = new StringBuilder(dir ~ "/" ~ name ~ ".main.asasm");
sb ~= "#version 4";
sb.newLine();
sb ~= "program";
sb.indent++; sb.newLine();
sb ~= "minorversion ";
sb ~= to!string(as.minorVersion);
sb.newLine();
sb ~= "majorversion ";
sb ~= to!string(as.majorVersion);
sb.newLine();
sb.newLine();
foreach (uint i, script; as.scripts)
{
auto ii = i; // http://d.puremagic.com/issues/show_bug.cgi?id=8526
newInclude(sb, refs.scripts.getFilename(script, "script"), (StringBuilder sb) {
dumpScript(sb, script, ii);
});
}
sb.newLine();
if (as.orphanClasses.length)
{
sb ~= "; ============================= Orphan classes ==============================";
sb.newLine();
sb.newLine();
foreach (i, vclass; as.orphanClasses)
newInclude(sb, refs.objects.getFilename(vclass, "class"), (StringBuilder sb) {
dumpClass(sb, vclass);
});
sb.newLine();
}
if (as.orphanMethods.length)
{
sb ~= "; ============================= Orphan methods ==============================";
sb.newLine();
sb.newLine();
foreach (i, method; as.orphanMethods)
newInclude(sb, refs.objects.getFilename(method, "method"), (StringBuilder sb) {
dumpMethod(sb, method, "method");
});
sb.newLine();
}
sb.indent--;
sb ~= "end ; program"; sb.newLine();
sb.save();
}
void dumpInt(StringBuilder sb, long v)
{
if (v == ABCFile.NULL_INT)
sb ~= "null";
else
sb.write(v);
}
void dumpUInt(StringBuilder sb, ulong v)
{
if (v == ABCFile.NULL_UINT)
sb ~= "null";
else
sb.write(v);
}
static struct StaticBuf(T, size_t size)
{
T[size] buf;
size_t pos;
void put(T v) { buf[pos++] = v; }
void put(in T[] v) { buf[pos..pos+v.length] = v[]; pos+=v.length; }
T[] data() { return buf[0..pos]; }
}
void dumpDouble(StringBuilder sb, double v)
{
if (v == ABCFile.NULL_DOUBLE)
sb ~= "null";
else
{
StaticBuf!(char, 64) buf;
formattedWrite(&buf, "%.18g", v);
char[] s = buf.data();
static double forceDouble(double d) { static double n; n = d; return n; }
if (s != "nan" && s != "inf" && s != "-inf")
{
if (forceDouble(to!double(s)) == v)
{
foreach_reverse (i; 1..s.length)
if (s[i]>='0' && s[i]<='8')
{
s[i]++;
if (forceDouble(to!double(s[0..i+1]))==v)
s = s[0..i+1];
else
s[i]--;
}
while (s.length>2 && s[$-1]!='.' && forceDouble(to!double(s[0..$-1]))==v)
s = s[0..$-1];
}
else
{
buf.pos = 0;
formattedWrite(&buf, "%.13a", v);
s = buf.data();
auto n = forceDouble(to!double(s));
assert(n == v,
"Can't print precise representation of double: %(%02X %) (%.18g) => %s => %(%02X %) (%.18g)".format(
cast(ubyte[])cast(void[])[v], v,
s,
cast(ubyte[])cast(void[])[n], n,
));
}
}
sb ~= s;
}
}
void dumpString(StringBuilder sb, string str)
{
if (str is null)
sb ~= "null";
else
{
static const char[16] hexDigits = "0123456789ABCDEF";
sb ~= '"';
foreach (c; str)
if (c == 0x0A)
sb ~= `\n`;
else
if (c == 0x0D)
sb ~= `\r`;
else
if (c == '\\')
sb ~= `\\`;
else
if (c == '"')
sb ~= `\"`;
else
if (c < 0x20)
{
sb ~= `\x`;
sb ~= hexDigits[c / 0x10];
sb ~= hexDigits[c % 0x10];
}
else
sb ~= c;
sb ~= '"';
}
}
void dumpNamespace(StringBuilder sb, ASProgram.Namespace namespace)
{
if (namespace is null)
sb ~= "null";
else
{
sb ~= ASTypeNames[namespace.kind];
sb ~= '(';
dumpString(sb, namespace.name);
if (refs.hasHomonyms(namespace))
{
sb ~= ", ";
auto label = refs.namespaces[namespace.kind].getName(namespace.id);
// label ~= format(" (%d)", namespace.id);
dumpString(sb, label);
}
sb ~= ')';
}
}
void dumpNamespaceSet(StringBuilder sb, ASProgram.Namespace[] set)
{
if (set is null)
sb ~= "null";
else
{
sb ~= '[';
foreach (i, ns; set)
{
dumpNamespace(sb, ns);
if (i < set.length-1)
sb ~= ", ";
}
sb ~= ']';
}
}
void dumpMultiname(StringBuilder sb, ASProgram.Multiname multiname)
{
if (multiname is null)
sb ~= "null";
else
with (multiname)
{
sb ~= ASTypeNames[kind];
sb ~= '(';
switch (kind)
{
case ASType.QName:
case ASType.QNameA:
dumpNamespace(sb, vQName.ns);
sb ~= ", ";
dumpString(sb, vQName.name);
break;
case ASType.RTQName:
case ASType.RTQNameA:
dumpString(sb, vRTQName.name);
break;
case ASType.RTQNameL:
case ASType.RTQNameLA:
break;
case ASType.Multiname:
case ASType.MultinameA:
dumpString(sb, vMultiname.name);
sb ~= ", ";
dumpNamespaceSet(sb, vMultiname.nsSet);
break;
case ASType.MultinameL:
case ASType.MultinameLA:
dumpNamespaceSet(sb, vMultinameL.nsSet);
break;
case ASType.TypeName:
dumpMultiname(sb, vTypeName.name);
sb ~= '<';
foreach (i, param; vTypeName.params)
{
dumpMultiname(sb, param);
if (i < vTypeName.params.length-1)
sb ~= ", ";
}
sb ~= '>';
break;
default:
throw new .Exception("Unknown Multiname kind");
}
sb ~= ')';
}
}
void dumpTraits(StringBuilder sb, ASProgram.Trait[] traits, bool inScript = false)
{
foreach (/*ref*/ trait; traits)
{
sb ~= "trait ";
sb ~= TraitKindNames[trait.kind];
sb ~= ' ';
dumpMultiname(sb, trait.name);
if (trait.attr)
dumpFlags!(true)(sb, trait.attr, TraitAttributeNames);
bool inLine = false;
switch (trait.kind)
{
case TraitKind.Slot:
case TraitKind.Const:
if (trait.vSlot.slotId)
{
sb ~= " slotid ";
dumpUInt(sb, trait.vSlot.slotId);
}
if (trait.vSlot.typeName)
{
sb ~= " type ";
dumpMultiname(sb, trait.vSlot.typeName);
}
if (trait.vSlot.value.vkind)
{
sb ~= " value ";
dumpValue(sb, trait.vSlot.value);
}
inLine = true;
break;
case TraitKind.Class:
if (trait.vClass.slotId)
{
sb ~= " slotid ";
dumpUInt(sb, trait.vClass.slotId);
}
sb.indent++; sb.newLine();
newInclude(sb, refs.objects.getFilename(trait.vClass.vclass, "class"), (StringBuilder sb) {
dumpClass(sb, trait.vClass.vclass);
});
break;
case TraitKind.Function:
if (trait.vFunction.slotId)
{
sb ~= " slotid ";
dumpUInt(sb, trait.vFunction.slotId);
}
sb.indent++; sb.newLine();
newInclude(sb, refs.objects.getFilename(trait.vFunction.vfunction, "method"), (StringBuilder sb) {
dumpMethod(sb, trait.vFunction.vfunction, "method");
}, inScript);
break;
case TraitKind.Method:
case TraitKind.Getter:
case TraitKind.Setter:
if (trait.vMethod.dispId)
{
sb ~= " dispid ";
dumpUInt(sb, trait.vMethod.dispId);
}
sb.indent++; sb.newLine();
newInclude(sb, refs.objects.getFilename(trait.vMethod.vmethod, "method"), (StringBuilder sb) {
dumpMethod(sb, trait.vMethod.vmethod, "method");
}, inScript);
break;
default:
throw new Exception("Unknown trait kind");
}
foreach (metadata; trait.metadata)
{
if (inLine)
{
sb.indent++; sb.newLine();
inLine = false;
}
dumpMetadata(sb, metadata);
}
if (inLine)
{ sb ~= " end"; sb.newLine(); }
else
{ sb.indent--; sb ~= "end ; trait"; sb.newLine(); }
}
}
void dumpMetadata(StringBuilder sb, ASProgram.Metadata metadata)
{
sb ~= "metadata ";
dumpString(sb, metadata.name);
sb.indent++; sb.newLine();
assert(metadata.keys.length == metadata.values.length);
foreach (i; 0..metadata.keys.length)
{
sb ~= "item ";
dumpString(sb, metadata.keys[i]);
sb ~= " ";
dumpString(sb, metadata.values[i]);
sb.newLine();
}
sb.indent--; sb ~= "end ; metadata"; sb.newLine();
}
void dumpFlags(bool oneLine = false)(StringBuilder sb, ubyte flags, const string[] names)
{
for (int i=0; flags; i++, flags>>=1)
if (flags & 1)
{
static if (oneLine)
sb ~= " flag ";
else
sb ~= "flag ";
sb ~= names[i];
static if (!oneLine)
sb.newLine();
}
}
void dumpValue(StringBuilder sb, ref ASProgram.Value value)
{
with (value)
{
sb ~= ASTypeNames[vkind];
sb ~= '(';
switch (vkind)
{
case ASType.Integer:
dumpInt(sb, vint);
break;
case ASType.UInteger:
dumpUInt(sb, vuint);
break;
case ASType.Double:
dumpDouble(sb, vdouble);
break;
case ASType.Utf8:
dumpString(sb, vstring);
break;
case ASType.Namespace:
case ASType.PackageNamespace:
case ASType.PackageInternalNs:
case ASType.ProtectedNamespace:
case ASType.ExplicitNamespace:
case ASType.StaticProtectedNs:
case ASType.PrivateNamespace:
dumpNamespace(sb, vnamespace);
break;
case ASType.True:
case ASType.False:
case ASType.Null:
case ASType.Undefined:
break;
default:
throw new Exception("Unknown type");
}
sb ~= ')';
}
}
void dumpMethod(StringBuilder sb, ASProgram.Method method, string label)
{
sb ~= label;
// sb ~= format(" ; %d", method.id);
sb.indent++; sb.newLine();
if (method.name !is null)
{
sb ~= "name ";
dumpString(sb, method.name);
sb.newLine();
}
auto refName = refs.objects.getName(method);
if (refName)
{
sb ~= "refid ";
dumpString(sb, refName);
sb.newLine();
}
foreach (m; method.paramTypes)
{
sb ~= "param ";
dumpMultiname(sb, m);
sb.newLine();
}
if (method.returnType)
{
sb ~= "returns ";
dumpMultiname(sb, method.returnType);
sb.newLine();
}
dumpFlags(sb, method.flags, MethodFlagNames);
foreach (ref v; method.options)
{
sb ~= "optional ";
dumpValue(sb, v);
sb.newLine();
}
foreach (s; method.paramNames)
{
sb ~= "paramname ";
dumpString(sb, s);
sb.newLine();
}
if (method.vbody)
dumpMethodBody(sb, method.vbody);
sb.indent--; sb ~= "end ; method"; sb.newLine();
}
void dumpClass(StringBuilder sb, ASProgram.Class vclass)
{
sb ~= "class"; sb.indent++; sb.newLine();
auto refName = refs.objects.getName(vclass);
if (refName)
{
sb ~= "refid ";
dumpString(sb, refName);
sb.newLine();
}
sb ~= "instance ";
dumpInstance(sb, vclass.instance);
dumpMethod(sb, vclass.cinit, "cinit");
dumpTraits(sb, vclass.traits);
sb.indent--; sb ~= "end ; class"; sb.newLine();
}
void dumpInstance(StringBuilder sb, ASProgram.Instance instance)
{
dumpMultiname(sb, instance.name);
sb.indent++; sb.newLine();
if (instance.superName)
{
sb ~= "extends ";
dumpMultiname(sb, instance.superName);
sb.newLine();
}
foreach (i; instance.interfaces)
{
sb ~= "implements ";
dumpMultiname(sb, i);
sb.newLine();
}
dumpFlags(sb, instance.flags, InstanceFlagNames);
if (instance.protectedNs)
{
sb ~= "protectedns ";
dumpNamespace(sb, instance.protectedNs);
sb.newLine();
}
dumpMethod(sb, instance.iinit, "iinit");
dumpTraits(sb, instance.traits);
sb.indent--; sb ~= "end ; instance"; sb.newLine();
}
void dumpScript(StringBuilder sb, ASProgram.Script script, uint index)
{
sb ~= "script";
sb.indent++; sb.newLine();
dumpMethod(sb, script.sinit, "sinit");
dumpTraits(sb, script.traits, true);
sb.indent--; sb ~= "end ; script"; sb.newLine();
}
void dumpUIntField(StringBuilder sb, string name, uint value)
{
sb ~= name;
sb ~= ' ';
dumpUInt(sb, value);
sb.newLine();
}
void dumpLabel(StringBuilder sb, ref ABCFile.Label label)
{
sb ~= 'L';
sb.write(label.index);
if (label.offset != 0)
{
if (label.offset > 0)
sb ~= '+';
sb.write(label.offset);
}
}
void dumpMethodBody(StringBuilder sb, ASProgram.MethodBody mbody)
{
sb ~= "body";
sb.indent++; sb.newLine();
dumpUIntField(sb, "maxstack", mbody.maxStack);
dumpUIntField(sb, "localcount", mbody.localCount);
dumpUIntField(sb, "initscopedepth", mbody.initScopeDepth);
dumpUIntField(sb, "maxscopedepth", mbody.maxScopeDepth);
sb ~= "code";
sb.newLine();
bool[] labels = new bool[mbody.instructions.length+1];
// reserve exception labels
foreach (ref e; mbody.exceptions)
labels[e.from.index] = labels[e.to.index] = labels[e.target.index] = true;
sb.indent++;
dumpInstructions(sb, mbody.instructions, labels, mbody.errors);
sb.indent--;
sb ~= "end ; code";
sb.newLine();
foreach (ref e; mbody.exceptions)
{
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 ";
dumpMultiname(sb, e.varName);
sb ~= " end";
sb.newLine();
}
dumpTraits(sb, mbody.traits);
sb.indent--; sb ~= "end ; body"; sb.newLine();
sb.linePrefix = null;
}
void dumpInstructions(StringBuilder sb, ASProgram.Instruction[] instructions, bool[] labels, ABCFile.Error[] errors)
{
foreach (ref instruction; instructions)
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
switch (type)
{
case OpcodeArgumentType.JumpTarget:
case OpcodeArgumentType.SwitchDefaultTarget:
labels[instruction.arguments[i].jumpTarget.index] = true;
break;
case OpcodeArgumentType.SwitchTargets:
foreach (ref label; instruction.arguments[i].switchTargets)
labels[label.index] = true;
break;
default:
break;
}
void checkLabel(uint ii)
{
if (labels[ii])
{
sb.noIndent();
sb ~= 'L';
sb.write(ii);
sb ~= ':';
sb.newLine();
}
}
string[] iErrors = new string[instructions.length + 1];
foreach (ref error; errors)
iErrors[error.loc.index] = error.msg;
bool extraNewLine = false;
foreach (uint ii, ref instruction; instructions)
{
if (extraNewLine)
sb.newLine();
extraNewLine = newLineAfter[instruction.opcode];
checkLabel(ii);
if (iErrors[ii])
{
sb ~= "; Error: ";
sb ~= iErrors[ii];
sb.newLine();
}
if (instruction.opcode == Opcode.OP_raw)
{
if (dumpRaw)
sb ~= "; 0x%02X".format(instruction.arguments[0].ubytev);
sb.newLine();
continue;
}
sb ~= opcodeInfo[instruction.opcode].name;
auto argTypes = opcodeInfo[instruction.opcode].argumentTypes;
if (argTypes.length)
{
foreach (i; opcodeInfo[instruction.opcode].name.length..20)
sb ~= ' ';
foreach (i, type; argTypes)
{
final switch (type)
{
case OpcodeArgumentType.Unknown:
throw new Exception("Don't know how to disassemble OP_" ~ opcodeInfo[instruction.opcode].name);
case OpcodeArgumentType.ByteLiteral:
sb.write(instruction.arguments[i].bytev);
break;
case OpcodeArgumentType.UByteLiteral:
sb.write(instruction.arguments[i].ubytev);
break;
case OpcodeArgumentType.IntLiteral:
sb.write(instruction.arguments[i].intv);
break;
case OpcodeArgumentType.UIntLiteral:
sb.write(instruction.arguments[i].uintv);
break;
case OpcodeArgumentType.Int:
dumpInt(sb, instruction.arguments[i].intv);
break;
case OpcodeArgumentType.UInt:
dumpUInt(sb, instruction.arguments[i].uintv);
break;
case OpcodeArgumentType.Double:
dumpDouble(sb, instruction.arguments[i].doublev);
break;
case OpcodeArgumentType.String:
dumpString(sb, instruction.arguments[i].stringv);
break;
case OpcodeArgumentType.Namespace:
dumpNamespace(sb, instruction.arguments[i].namespacev);
break;
case OpcodeArgumentType.Multiname:
dumpMultiname(sb, instruction.arguments[i].multinamev);
break;
case OpcodeArgumentType.Class:
if (instruction.arguments[i].classv is null)
sb ~= "null";
else
dumpString(sb, refs.objects.getName(instruction.arguments[i].classv));
break;
case OpcodeArgumentType.Method:
if (instruction.arguments[i].methodv is null)
sb ~= "null";
else
dumpString(sb, refs.objects.getName(instruction.arguments[i].methodv));
break;
case OpcodeArgumentType.JumpTarget:
case OpcodeArgumentType.SwitchDefaultTarget:
dumpLabel(sb, instruction.arguments[i].jumpTarget);
break;
case OpcodeArgumentType.SwitchTargets:
sb ~= '[';
auto targets = instruction.arguments[i].switchTargets;
foreach (ti, t; targets)
{
dumpLabel(sb, t);
if (ti < targets.length-1)
sb ~= ", ";
}
sb ~= ']';
break;
}
if (i < argTypes.length-1)
sb ~= ", ";
}
}
sb.newLine();
}
checkLabel(to!uint(instructions.length));
}
}
private:
bool nsSimilar(ASProgram.Namespace ns1, ASProgram.Namespace ns2)
{
if (ns1.kind==ASType.PrivateNamespace || ns2.kind==ASType.PrivateNamespace)
return ns1.kind==ns2.kind && ns1.id==ns2.id;
// ignore ns kind in other cases
return ns1.name == ns2.name;
}
bool[256] newLineAfter;
static this()
{
foreach (o; [
Opcode.OP_callpropvoid,
Opcode.OP_constructsuper,
Opcode.OP_initproperty,
Opcode.OP_ifeq,
Opcode.OP_iffalse,
Opcode.OP_ifge,
Opcode.OP_ifgt,
Opcode.OP_ifle,
Opcode.OP_iflt,
Opcode.OP_ifne,
Opcode.OP_ifnge,
Opcode.OP_ifngt,
Opcode.OP_ifnle,
Opcode.OP_ifnlt,
Opcode.OP_ifstricteq,
Opcode.OP_ifstrictne,
Opcode.OP_iftrue,
Opcode.OP_jump,
Opcode.OP_lookupswitch,
Opcode.OP_pushscope,
Opcode.OP_returnvalue,
Opcode.OP_returnvoid,
Opcode.OP_setglobalslot,
Opcode.OP_setlocal,
Opcode.OP_setlocal0,
Opcode.OP_setlocal1,
Opcode.OP_setlocal2,
Opcode.OP_setlocal3,
Opcode.OP_setproperty,
Opcode.OP_setpropertylate,
Opcode.OP_setslot,
Opcode.OP_setsuper,
Opcode.OP_si8,
Opcode.OP_si16,
Opcode.OP_si32,
Opcode.OP_sf32,
Opcode.OP_sf64,
Opcode.OP_throw,
])
newLineAfter[o] = true;
}
/// Force a raw data join (workaround for issue 6064)
T[] arrayJoin(T)(T[][] arrays, T[] sep)
{
return cast(T[])join(cast(ubyte[][])arrays, cast(ubyte[])sep);
}
/// ditto
T[] arrayJoin(T)(T[][] arrays)
{
return cast(T[])join(cast(ubyte[][])arrays);
}