Encoded source presentation changes.

Now encoded source does not encode sources of nested functions as external references. Rather they are encoded as any other source elements and to implement function.toString() runtime stores starting and ending offsets for function encoded source.

In this way decompiler is separated from the rest of runtime and external applications can access the encoded source with less efforts.
This commit is contained in:
igor%mir2.org 2003-08-12 11:50:48 +00:00
Родитель 494a57b800
Коммит ec958a8bd4
11 изменённых файлов: 194 добавлений и 227 удалений

Просмотреть файл

@ -1931,6 +1931,11 @@ public class Context {
ScriptOrFnNode tree = p.parse(ts, irf, decompiler);
if (tree == null)
return null;
String encodedSource = null;
if (isGeneratingSource()) {
encodedSource = decompiler.getEncodedSource();
}
decompiler = null; // It helps GC
tree = compiler.transform(this, irf, tree);
@ -1944,7 +1949,8 @@ public class Context {
}
Object result = compiler.compile(this, scope, tree,
securityController, securityDomain);
securityController, securityDomain,
encodedSource);
if (debugger != null) {
if (sourceString == null) Context.codeBug();

Просмотреть файл

@ -80,7 +80,9 @@ package org.mozilla.javascript;
*/
public class Decompiler
{
private static final int FUNCTION_REF = Token.LAST_TOKEN + 1;
// Marker to denote the last RC of function so it can be distinguished from
// the last RC of object literals in case of function expressions
private static final int FUNCTION_END = Token.LAST_TOKEN + 1;
public Decompiler()
{
@ -91,21 +93,21 @@ public class Decompiler
return sourceToString(0);
}
int startFunction()
int getCurrentOffset()
{
// Add reference to the enclosing function/script
append((char)FUNCTION_REF);
return sourceTop;
}
String stopFunction(int savedTop)
int markFunctionStart()
{
if (!(0 <= savedTop && savedTop <= sourceTop))
throw new IllegalArgumentException();
return getCurrentOffset();
}
String encoded = sourceToString(savedTop);
sourceTop = savedTop;
return encoded;
int markFunctionEnd(int functionStart)
{
int offset = getCurrentOffset();
append((char)FUNCTION_END);
return offset;
}
void addToken(int token)
@ -285,32 +287,14 @@ public class Decompiler
* @param indentGap the identation offset for case labels
*
*/
static String decompile(Object encodedSourcesTree,
static String decompile(String source,
boolean justFunctionBody,
int indent, int indentGap, int caseGap)
{
StringBuffer result = new StringBuffer();
decompile_r(encodedSourcesTree, false, justFunctionBody,
indent, indentGap, caseGap, result);
return result.toString();
}
private static void decompile_r(Object encodedSourcesTree,
boolean nested, boolean justFunctionBody,
int indent, int indentGap, int caseGap,
StringBuffer result)
{
String source;
Object[] childNodes = null;
if (encodedSourcesTree instanceof String) {
source = (String)encodedSourcesTree;
} else {
childNodes = (Object[])encodedSourcesTree;
source = (String)childNodes[0];
}
int length = source.length();
if (length == 0) { return; }
if (length == 0) { return ""; }
StringBuffer result = new StringBuffer();
// Spew tokens in source, for debugging.
// as TYPE number char
@ -339,26 +323,15 @@ public class Decompiler
System.err.println();
}
if (!nested) {
// add an initial newline to exactly match js.
result.append('\n');
for (int j = 0; j < indent; j++)
result.append(' ');
}
// add an initial newline to exactly match js.
result.append('\n');
for (int j = 0; j < indent; j++)
result.append(' ');
int functionCount = 0;
int braceNesting = 0;
boolean afterFirstEOL = false;
int i = 0;
boolean skipFunctionHeader = false;
if (source.charAt(0) == Token.FUNCTION) {
++i;
result.append("function ");
if (justFunctionBody) {
skipFunctionHeader = true;
}
}
while (i < length) {
switch(source.charAt(i)) {
case Token.NAME:
@ -407,21 +380,12 @@ public class Decompiler
}
break;
case FUNCTION_REF:
++functionCount;
/* decompile a FUNCTION token as an escape; call
* toString on the encoded source at
* childNodes[1 + functionIndex] or childNodes[functionCount]
*/
if (childNodes == null
|| functionCount >= childNodes.length)
{
throw Context.reportRuntimeError(Context.getMessage1
("msg.no.function.ref.found",
new Integer(functionCount - 1)));
}
decompile_r(childNodes[functionCount], true, false,
indent, indentGap, caseGap, result);
case Token.FUNCTION:
result.append("function ");
break;
case FUNCTION_END:
// Do nothing
break;
case Token.COMMA:
@ -430,41 +394,42 @@ public class Decompiler
case Token.LC:
++braceNesting;
if (nextIs(source, length, i, Token.EOL))
if (Token.EOL == getNext(source, length, i))
indent += indentGap;
result.append('{');
break;
case Token.RC:
case Token.RC: {
--braceNesting;
/* don't print the closing RC if it closes the
* toplevel function and we're called from
* decompileFunctionBody.
*/
if (justFunctionBody && !nested && braceNesting == 0)
if (justFunctionBody && braceNesting == 0)
break;
if (nextIs(source, length, i, Token.EOL))
indent -= indentGap;
result.append('}');
if (nextIs(source, length, i, Token.WHILE)
|| nextIs(source, length, i, Token.ELSE))
{
indent -= indentGap;
result.append(' ');
switch (getNext(source, length, i)) {
case Token.EOL:
case FUNCTION_END:
indent -= indentGap;
break;
case Token.WHILE:
case Token.ELSE:
indent -= indentGap;
result.append(' ');
break;
}
break;
}
case Token.LP:
result.append('(');
break;
case Token.RP:
if (nextIs(source, length, i, Token.LC))
result.append(") ");
else
result.append(')');
result.append(')');
if (Token.LC == getNext(source, length, i))
result.append(' ');
break;
case Token.LB:
@ -475,15 +440,20 @@ public class Decompiler
result.append(']');
break;
case Token.EOL:
if (skipFunctionHeader) {
skipFunctionHeader = false;
/* throw away just added 'function name(...) {' and restore
* the original indent
*/
result.setLength(0);
indent -= indentGap;
} else {
case Token.EOL: {
boolean newLine = true;
if (!afterFirstEOL) {
afterFirstEOL = true;
if (justFunctionBody) {
/* throw away just added 'function name(...) {'
* and restore the original indent
*/
result.setLength(0);
indent -= indentGap;
newLine = false;
}
}
if (newLine) {
result.append('\n');
}
@ -515,7 +485,7 @@ public class Decompiler
result.append(' ');
}
break;
}
case Token.DOT:
result.append('.');
break;
@ -577,17 +547,15 @@ public class Decompiler
break;
case Token.BREAK:
if (nextIs(source, length, i, Token.NAME))
result.append("break ");
else
result.append("break");
result.append("break");
if (Token.NAME == getNext(source, length, i))
result.append(' ');
break;
case Token.CONTINUE:
if (nextIs(source, length, i, Token.NAME))
result.append("continue ");
else
result.append("continue");
result.append("continue");
if (Token.NAME == getNext(source, length, i))
result.append(' ');
break;
case Token.CASE:
@ -599,10 +567,9 @@ public class Decompiler
break;
case Token.RETURN:
if (nextIs(source, length, i, Token.SEMI))
result.append("return");
else
result.append("return ");
result.append("return");
if (Token.SEMI != getNext(source, length, i))
result.append(' ');
break;
case Token.VAR:
@ -610,12 +577,11 @@ public class Decompiler
break;
case Token.SEMI:
if (nextIs(source, length, i, Token.EOL))
// statement termination
result.append(';');
else
result.append(';');
if (Token.EOL != getNext(source, length, i)) {
// separators in FOR
result.append("; ");
result.append(' ');
}
break;
case Token.ASSIGN:
@ -684,7 +650,7 @@ public class Decompiler
break;
case Token.COLON:
if (nextIs(source, length, i, Token.EOL))
if (Token.EOL == getNext(source, length, i))
// it's the end of a label
result.append(':');
else
@ -840,13 +806,15 @@ public class Decompiler
}
// add that trailing newline if it's an outermost function.
if (!nested && !justFunctionBody)
if (!justFunctionBody)
result.append('\n');
return result.toString();
}
private static boolean nextIs(String source, int length, int i, int token)
private static int getNext(String source, int length, int i)
{
return (i + 1 < length) ? source.charAt(i + 1) == token : false;
return (i + 1 < length) ? source.charAt(i + 1) : Token.EOF;
}
private static int getSourceStringEnd(String source, int offset)

Просмотреть файл

@ -56,15 +56,8 @@ public class IRFactory {
/**
* Script (for associating file/url names with toplevel scripts.)
*/
public void
initScript(ScriptOrFnNode scriptNode, Object body,
String sourceName, int baseLineno, int endLineno, String source)
public void initScript(ScriptOrFnNode scriptNode, Object body)
{
scriptNode.setEncodedSource(source);
scriptNode.setSourceName(sourceName);
scriptNode.setBaseLineno(baseLineno);
scriptNode.setEndLineno(endLineno);
Node children = ((Node) body).getFirstChild();
if (children != null) { scriptNode.addChildrenToBack(children); }
scriptNode.finishParsing(this);
@ -213,15 +206,8 @@ public class IRFactory {
}
public Object initFunction(FunctionNode fnNode, int functionIndex,
Object statements,
String sourceName, int baseLineno,
int endLineno, String source,
int functionType)
Object statements, int functionType)
{
fnNode.setEncodedSource(source);
fnNode.setSourceName(sourceName);
fnNode.setBaseLineno(baseLineno);
fnNode.setEndLineno(endLineno);
fnNode.setFunctionType(functionType);
fnNode.addChildToBack((Node)statements);
fnNode.finishParsing(this);

Просмотреть файл

@ -43,7 +43,8 @@ final class InterpretedFunction extends NativeFunction
static final long serialVersionUID = -6235150451107527319L;
InterpretedFunction(Context cx, InterpreterData theData) {
InterpretedFunction(Context cx, InterpreterData theData)
{
itsData = theData;
functionName = itsData.itsName;
version = (short)cx.getLanguageVersion();
@ -60,8 +61,9 @@ final class InterpretedFunction extends NativeFunction
this, itsData);
}
protected Object getSourcesTree() {
return Interpreter.getSourcesTree(itsData);
public String getEncodedSource()
{
return Interpreter.getEncodedSource(itsData);
}
InterpreterData itsData;

Просмотреть файл

@ -64,8 +64,9 @@ final class InterpretedScript extends NativeScript
this, itsData);
}
protected Object getSourcesTree() {
return Interpreter.getSourcesTree(itsData);
public String getEncodedSource()
{
return Interpreter.getEncodedSource(itsData);
}
InterpreterData itsData;

Просмотреть файл

@ -116,12 +116,13 @@ public class Interpreter
public Object compile(Context cx, Scriptable scope, ScriptOrFnNode tree,
SecurityController securityController,
Object securityDomain)
Object securityDomain, String encodedSource)
{
scriptOrFn = tree;
version = cx.getLanguageVersion();
itsData = new InterpreterData(securityDomain);
itsData.itsSourceFile = scriptOrFn.getSourceName();
itsData.encodedSource = encodedSource;
if (tree instanceof FunctionNode) {
generateFunctionICode(cx, scope);
return createFunction(cx, scope, itsData, false);
@ -232,7 +233,8 @@ public class Interpreter
itsData.argNames = scriptOrFn.getParamAndVarNames();
itsData.argCount = scriptOrFn.getParamCount();
itsData.itsSource = scriptOrFn.getEncodedSource();
itsData.encodedSourceStart = scriptOrFn.getEncodedSourceStart();
itsData.encodedSourceEnd = scriptOrFn.getEncodedSourceEnd();
if (Token.printICode) dumpICode(itsData);
}
@ -249,6 +251,7 @@ public class Interpreter
jsi.scriptOrFn = def;
jsi.itsData = new InterpreterData(itsData.securityDomain);
jsi.itsData.itsSourceFile = itsData.itsSourceFile;
jsi.itsData.encodedSource = itsData.encodedSource;
jsi.itsData.itsCheckThis = def.getCheckThis();
jsi.itsInFunctionFlag = true;
jsi.generateFunctionICode(cx, scope);
@ -1593,19 +1596,13 @@ public class Interpreter
return presentLines.getKeys();
}
static Object getSourcesTree(InterpreterData idata) {
InterpreterData[] nested = idata.itsNestedFunctions;
if (nested == null || nested.length == 0) {
return idata.itsSource;
} else {
int N = nested.length;
Object[] result = new Object[N + 1];
result[0] = idata.itsSource;
for (int i = 0; i != N; ++i) {
result[1 + i] = getSourcesTree(nested[i]);
}
return result;
static String getEncodedSource(InterpreterData idata)
{
if (idata.encodedSource == null) {
return null;
}
return idata.encodedSource.substring(idata.encodedSourceStart,
idata.encodedSourceEnd);
}
private static InterpretedFunction createFunction(Context cx,
@ -3064,7 +3061,6 @@ public class Interpreter
private static final int EXCEPTION_WITH_DEPTH_SLOT = 4;
private int version;
private boolean inLineStepMode;
private static final Object DBL_MRK = new Object();
}

Просмотреть файл

@ -58,7 +58,6 @@ final class InterpreterData implements Serializable, DebuggableScript {
}
String itsName;
String itsSource;
String itsSourceFile;
boolean itsNeedsActivation;
boolean itsFromEvalCode;
@ -88,6 +87,10 @@ final class InterpreterData implements Serializable, DebuggableScript {
Object securityDomain;
String encodedSource;
int encodedSourceStart;
int encodedSourceEnd;
public boolean isFunction() {
return itsFunctionType != 0;
}

Просмотреть файл

@ -57,13 +57,13 @@ public class NativeFunction extends BaseFunction
*/
public String decompile(Context cx, int indent, boolean justbody)
{
Object encodedSourcesTree = getSourcesTree();
if (encodedSourcesTree == null) {
String encodedSource = getEncodedSource();
if (encodedSource == null) {
return super.decompile(cx, indent, justbody);
} else {
final int INDENT_GAP = 4;
final int CASE_GAP = 2; // less how much for case labels
return Decompiler.decompile(encodedSourcesTree, justbody,
return Decompiler.decompile(encodedSource, justbody,
indent, INDENT_GAP, CASE_GAP);
}
}
@ -95,20 +95,17 @@ public class NativeFunction extends BaseFunction
}
/**
* Get encoded source as tree where node data encodes source of single
* function as String. If node is String, it is leaf and its data is node
* itself. Otherwise node is Object[] array, where array[0] holds node data
* and array[1..array.length) holds child nodes.
* Get encoded source string.
*/
protected Object getSourcesTree()
public String getEncodedSource()
{
// The following is used only by optimizer, but is here to avoid
// introduction of 2 additional classes there
Class cl = getClass();
try {
Method m = cl.getDeclaredMethod("getSourcesTreeImpl",
Method m = cl.getDeclaredMethod("getEncodedSourceImpl",
new Class[0]);
return m.invoke(null, ScriptRuntime.emptyArgs);
return (String)m.invoke(null, ScriptRuntime.emptyArgs);
} catch (NoSuchMethodException ex) {
// No source implementation
return null;

Просмотреть файл

@ -504,8 +504,8 @@ public class Node implements Cloneable {
sb.append(" [source name: ");
sb.append(sof.getSourceName());
sb.append("] [encoded source length: ");
String encodedSource = sof.getEncodedSource();
sb.append(encodedSource != null ? encodedSource.length() : 0);
sb.append(sof.getEncodedSourceEnd()
- sof.getEncodedSourceStart());
sb.append("] [base line: ");
sb.append(sof.getBaseLineno());
sb.append("] [end line: ");

Просмотреть файл

@ -99,9 +99,10 @@ class Parser {
Decompiler decompiler)
throws IOException
{
this.decompiler = decompiler;
this.nf = nf;
currentScriptOrFn = nf.createScript();
this.decompiler = decompiler;
int sourceStartOffset = decompiler.getCurrentOffset();
this.ok = true;
@ -141,11 +142,16 @@ class Parser {
return null;
}
String source = decompiler.getEncodedSource();
this.decompiler = null; // To help GC
currentScriptOrFn.setSourceName(ts.getSourceName());
currentScriptOrFn.setBaseLineno(baseLineno);
currentScriptOrFn.setEndLineno(ts.getLineno());
int sourceEndOffset = decompiler.getCurrentOffset();
currentScriptOrFn.setEncodedSourceBounds(sourceStartOffset,
sourceEndOffset);
nf.initScript(currentScriptOrFn, pn);
nf.initScript(currentScriptOrFn, pn, ts.getSourceName(),
baseLineno, ts.getLineno(), source);
return currentScriptOrFn;
}
@ -231,7 +237,8 @@ class Parser {
FunctionNode fnNode = nf.createFunction(name);
int functionIndex = currentScriptOrFn.addFunction(fnNode);
int functionSourceOffset = decompiler.startFunction();
int functionSourceStart = decompiler.markFunctionStart();
int functionSourceEnd;
ScriptOrFnNode savedScriptOrFn = currentScriptOrFn;
currentScriptOrFn = fnNode;
@ -268,47 +275,47 @@ class Parser {
decompiler.addEOL(Token.LC);
body = parseFunctionBody(ts);
mustMatchToken(ts, Token.RC, "msg.no.brace.after.body");
decompiler.addToken(Token.RC);
// skip the last EOL so nested functions work...
// name might be null;
functionSourceEnd = decompiler.markFunctionEnd(functionSourceStart);
if (functionType != FunctionNode.FUNCTION_EXPRESSION) {
checkWellTerminatedFunction(ts);
if (memberExprNode == null) {
// Add EOL only if function is not part of expression
// since it gets SEMI + EOL from Statement in that case
decompiler.addToken(Token.EOL);
} else {
// Add ';' to make 'function x.f(){}'
// and 'x.f = function(){}'
// to print the same strings when decompiling
decompiler.addEOL(Token.SEMI);
}
}
}
finally {
source = decompiler.stopFunction(functionSourceOffset);
currentScriptOrFn = savedScriptOrFn;
}
fnNode.setEncodedSourceBounds(functionSourceStart, functionSourceEnd);
fnNode.setSourceName(ts.getSourceName());
fnNode.setBaseLineno(baseLineno);
fnNode.setEndLineno(ts.getLineno());
Object pn;
if (memberExprNode == null) {
pn = nf.initFunction(fnNode, functionIndex, body,
ts.getSourceName(),
baseLineno, ts.getLineno(),
source,
functionType);
pn = nf.initFunction(fnNode, functionIndex, body, functionType);
if (functionType == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) {
// The following can be removed but then code generators should
// be modified not to push on the stack function expression
// statements
pn = nf.createExprStatementNoReturn(pn, baseLineno);
}
// Add EOL but only if function is not part of expression, in which
// case it gets SEMI + EOL from Statement.
if (functionType != FunctionNode.FUNCTION_EXPRESSION) {
decompiler.addToken(Token.EOL);
checkWellTerminatedFunction(ts);
}
} else {
pn = nf.initFunction(fnNode, functionIndex, body,
ts.getSourceName(),
baseLineno, ts.getLineno(),
source,
FunctionNode.FUNCTION_EXPRESSION);
pn = nf.createBinary(Token.ASSIGN, Token.NOP, memberExprNode, pn);
if (functionType != FunctionNode.FUNCTION_EXPRESSION) {
pn = nf.createExprStatement(pn, baseLineno);
// Add ';' to make 'function x.f(){}' and 'x.f = function(){}'
// to print the same strings when decompiling
decompiler.addEOL(Token.SEMI);
checkWellTerminatedFunction(ts);
}
}
return pn;

Просмотреть файл

@ -130,10 +130,11 @@ public class Codegen extends Interpreter {
public Object compile(Context cx, Scriptable scope,
ScriptOrFnNode scriptOrFn,
SecurityController securityController,
Object securityDomain)
Object securityDomain, String encodedSource)
{
ObjArray classFiles = new ObjArray();
ObjArray names = new ObjArray();
this.encodedSource = encodedSource;
generateCode(cx, scriptOrFn, names, classFiles);
boolean onlySave = false;
@ -727,48 +728,46 @@ public class Codegen extends Interpreter {
// (per function/script, starting from 0.)
// Change Parser if changing ordering.
if (cx.isGeneratingSource()) {
String source = scriptOrFn.getEncodedSource();
if (source != null && source.length() < 65536) {
short flags = ClassFileWriter.ACC_PUBLIC
| ClassFileWriter.ACC_STATIC;
String getSourceMethodStr = "getSourcesTreeImpl";
if (mainCodegen.encodedSource != null) {
// generate
// public static String getEncodedSourceImpl()
// {
// return main_class.getEncodedSourceImpl(START, END);
// }
short flags = ClassFileWriter.ACC_PUBLIC
| ClassFileWriter.ACC_STATIC;
String getSourceMethodStr = "getEncodedSourceImpl";
String mainImplSig = "(II)Ljava/lang/String;";
classFile.startMethod(getSourceMethodStr,
"()Ljava/lang/String;",
(short)flags);
push(scriptOrFn.getEncodedSourceStart());
push(scriptOrFn.getEncodedSourceEnd());
classFile.addInvoke(ByteCode.INVOKESTATIC,
mainCodegen.generatedClassName,
getSourceMethodStr,
mainImplSig);
addByteCode(ByteCode.ARETURN);
// 0: no this and no argument
classFile.stopMethod((short)0, null);
if (isMainCodegen) {
// generate
// public static String getEncodedSourceImpl(int start, int end)
// {
// return ENCODED.substring(start, end);
// }
classFile.startMethod(getSourceMethodStr,
"()Ljava/lang/Object;",
mainImplSig,
(short)flags);
int functionCount = scriptOrFn.getFunctionCount();
if (functionCount == 0) {
// generate return <source-literal-string>;
push(source);
} else {
// generate
// Object[] result = new Object[1 + functionCount];
// result[0] = <source-literal-string>
// result[1] = Class1.getSourcesTreeImpl();
// ...
// result[functionCount] = ClassN.getSourcesTreeImpl();
// return result;
push(1 + functionCount);
addByteCode(ByteCode.ANEWARRAY, "java/lang/Object");
addByteCode(ByteCode.DUP); // dup array reference
push(0);
push(source);
addByteCode(ByteCode.AASTORE);
for (int i = 0; i != functionCount; ++i) {
OptFunctionNode fn;
addByteCode(ByteCode.DUP); // dup array reference
push(1 + i);
fn = (OptFunctionNode)scriptOrFn.getFunctionNode(i);
classFile.addInvoke(ByteCode.INVOKESTATIC,
fn.getClassName(),
getSourceMethodStr,
"()Ljava/lang/Object;");
addByteCode(ByteCode.AASTORE);
}
}
push(encodedSource);
addByteCode(ByteCode.ILOAD_0);
addByteCode(ByteCode.ILOAD_1);
classFile.addInvoke(ByteCode.INVOKEVIRTUAL,
"java/lang/String",
"substring",
"(II)Ljava/lang/String;");
addByteCode(ByteCode.ARETURN);
// 0: no this and no argument
classFile.stopMethod((short)0, null);
classFile.stopMethod((short)2, null);
}
}
}
@ -3839,6 +3838,8 @@ public class Codegen extends Interpreter {
private String itsSourceFile;
private int itsLineNumber;
private String encodedSource;
private int stackDepth;
private int stackDepthMax;