AS3 RegExp literal decompilation and direct editation

This commit is contained in:
Jindra Petřík
2016-01-12 20:45:01 +01:00
parent c2ce312370
commit edad412fc1
14 changed files with 1007 additions and 777 deletions

View File

@@ -30,6 +30,7 @@ import com.jpexs.decompiler.flash.abc.avm2.model.FindPropertyAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.FullMultinameAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.GetLexAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.GetPropertyAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.RegExpAvm2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.StringAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.XMLAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.operations.AddAVM2Item;
@@ -114,6 +115,33 @@ public class ConstructIns extends InstructionDefinition {
}
}
boolean isRegExp = false;
if (obj instanceof GetPropertyAVM2Item) {
GetPropertyAVM2Item gpt = (GetPropertyAVM2Item) obj;
if (gpt.object instanceof FindPropertyAVM2Item) {
FindPropertyAVM2Item fpt = (FindPropertyAVM2Item) gpt.object;
FullMultinameAVM2Item fptRegExpMult = (FullMultinameAVM2Item) fpt.propertyName;
FullMultinameAVM2Item gptRegExpMult = (FullMultinameAVM2Item) gpt.propertyName;
isRegExp = fptRegExpMult.isTopLevel("RegExp", localData.getConstants(), localData.localRegNames, localData.fullyQualifiedNames)
&& gptRegExpMult.isTopLevel("RegExp", localData.getConstants(), localData.localRegNames, localData.fullyQualifiedNames);
}
}
if (obj instanceof GetLexAVM2Item) {
GetLexAVM2Item glt = (GetLexAVM2Item) obj;
isRegExp = glt.propertyName.getName(localData.getConstants(), localData.fullyQualifiedNames, true).equals("RegExp");
}
if (isRegExp && (args.size() >= 1) && (args.get(0) instanceof StringAVM2Item) && (args.size() == 1 || (args.size() == 2 && args.get(1) instanceof StringAVM2Item))) {
String pattern = ((StringAVM2Item) args.get(0)).getValue();
String modifiers = "";
if (args.size() == 2) {
modifiers = ((StringAVM2Item) args.get(1)).getValue();
}
stack.push(new RegExpAvm2Item(pattern, modifiers, ins, localData.lineStartInstruction));
return;
}
stack.push(new ConstructAVM2Item(ins, localData.lineStartInstruction, obj, args));
}

View File

@@ -26,6 +26,8 @@ import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition;
import com.jpexs.decompiler.flash.abc.avm2.model.ConstructPropAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.FindPropertyAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.FullMultinameAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.RegExpAvm2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.StringAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.XMLAVM2Item;
import com.jpexs.decompiler.graph.GraphTargetItem;
import com.jpexs.decompiler.graph.TranslateStack;
@@ -81,6 +83,19 @@ public class ConstructPropIns extends InstructionDefinition {
return;
}
}
}//
boolean isRegExp = false;
if (multiname.isTopLevel("RegExp", localData.getConstants(), localData.localRegNames, localData.fullyQualifiedNames)) {
isRegExp = true;
}
if (isRegExp && (args.size() >= 1) && (args.get(0) instanceof StringAVM2Item) && (args.size() == 1 || (args.size() == 2 && args.get(1) instanceof StringAVM2Item))) {
String pattern = ((StringAVM2Item) args.get(0)).getValue();
String modifiers = "";
if (args.size() == 2) {
modifiers = ((StringAVM2Item) args.get(1)).getValue();
}
stack.push(new RegExpAvm2Item(pattern, modifiers, ins, localData.lineStartInstruction));
return;
}
stack.push(new ConstructPropAVM2Item(ins, localData.lineStartInstruction, obj, multiname, args));

View File

@@ -70,7 +70,7 @@ public class FullMultinameAVM2Item extends AVM2Item {
return (name != null) || (namespace != null);
}
public boolean isXML(AVM2ConstantPool constants, HashMap<Integer, String> localRegNames, List<DottedChain> fullyQualifiedNames) throws InterruptedException {
public boolean isTopLevel(String tname, AVM2ConstantPool constants, HashMap<Integer, String> localRegNames, List<DottedChain> fullyQualifiedNames) throws InterruptedException {
String cname;
if (name != null) {
cname = name.toString(LocalData.create(constants, localRegNames, fullyQualifiedNames));
@@ -86,7 +86,11 @@ public class FullMultinameAVM2Item extends AVM2Item {
cns = ns.getName(constants).toPrintableString(true);
}
}
return cname.equals("XML") && cns.isEmpty();
return cname.equals(tname) && cns.isEmpty();
}
public boolean isXML(AVM2ConstantPool constants, HashMap<Integer, String> localRegNames, List<DottedChain> fullyQualifiedNames) throws InterruptedException {
return isTopLevel("XML", constants, localRegNames, fullyQualifiedNames);
}
@Override

View File

@@ -0,0 +1,105 @@
package com.jpexs.decompiler.flash.abc.avm2.model;
import com.jpexs.decompiler.flash.SourceGeneratorLocalData;
import com.jpexs.decompiler.flash.abc.ABC;
import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool;
import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instructions;
import com.jpexs.decompiler.flash.abc.avm2.instructions.other.GetLexIns;
import com.jpexs.decompiler.flash.abc.avm2.parser.script.AVM2SourceGenerator;
import com.jpexs.decompiler.flash.abc.types.Namespace;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.graph.CompilationException;
import com.jpexs.decompiler.graph.GraphSourceItem;
import com.jpexs.decompiler.graph.GraphTargetItem;
import com.jpexs.decompiler.graph.SourceGenerator;
import com.jpexs.decompiler.graph.TypeItem;
import com.jpexs.decompiler.graph.model.LocalData;
import com.jpexs.helpers.Helper;
import java.util.List;
/**
*
* @author JPEXS
*/
public class RegExpAvm2Item extends AVM2Item {
public String pattern;
public String modifier;
public RegExpAvm2Item(String pattern, String modifier, GraphSourceItem instruction, GraphSourceItem lineStartIns) {
super(instruction, lineStartIns, PRECEDENCE_PRIMARY);
this.pattern = pattern;
this.modifier = modifier;
}
public static String escapeRegExpString(String s) {
StringBuilder ret = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '\n') {
ret.append("\\n");
} else if (c == '\r') {
ret.append("\\r");
} else if (c == '\t') {
ret.append("\\t");
} else if (c == '\b') {
ret.append("\\b");
} else if (c == '\f') {
ret.append("\\f");
} else if (c == '/') {
ret.append("\\/");
} else if (c < 32) {
ret.append("\\x").append(Helper.byteToHex((byte) c));
} else {
ret.append(c);
}
}
return ret.toString();
}
@Override
public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException {
if (Configuration.useRegExprLiteral.get()) {
writer.append("/");
writer.append(escapeRegExpString(pattern));
writer.append("/");
writer.append(modifier);
} else {
writer.append("new RegExp(");
writer.append("\"" + Helper.escapeActionScriptString(pattern) + "\"");
if (!(modifier == null || modifier.isEmpty())) {
writer.append(",");
writer.append("\"" + modifier + "\"");
}
writer.append(")");
}
return writer;
}
@Override
public boolean hasReturnValue() {
return true;
}
@Override
public GraphTargetItem returnType() {
return new TypeItem("RegExp");
}
@Override
public List<GraphSourceItem> toSource(SourceGeneratorLocalData localData, SourceGenerator generator) throws CompilationException {
AVM2SourceGenerator g = (AVM2SourceGenerator) generator;
ABC abc = g.abcIndex.getSelectedAbc();
AVM2ConstantPool constants = abc.constants;
boolean hasModifier = !(modifier == null || modifier.isEmpty());
return toSourceMerge(localData, generator,
ins(AVM2Instructions.GetLex, constants.getQnameId("RegExp", Namespace.KIND_PACKAGE, "", true)),
new StringAVM2Item(null, null, pattern),
hasModifier ? new StringAVM2Item(null, null, modifier) : null,
ins(AVM2Instructions.Construct, hasModifier ? 2 : 1)
);
}
}

View File

@@ -41,6 +41,7 @@ import com.jpexs.decompiler.flash.abc.avm2.model.NewObjectAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.NullAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.PostDecrementAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.PostIncrementAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.RegExpAvm2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.ReturnValueAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.ReturnVoidAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.StringAVM2Item;
@@ -504,10 +505,8 @@ public class ActionScript3Parser {
if (s.type == SymbolType.ASSIGN) {
paramValues.add(expression(allOpenedNamespaces, thisType, pkg, new Reference<>(false), importedClasses, openedNamespaces, null, isMethod, isMethod, isMethod, variables));
s = lex();
} else {
if (!paramValues.isEmpty()) {
throw new AVM2ParseException("Some of parameters do not have default values", lexer.yyline());
}
} else if (!paramValues.isEmpty()) {
throw new AVM2ParseException("Some of parameters do not have default values", lexer.yyline());
}
}
@@ -661,10 +660,8 @@ public class ActionScript3Parser {
throw new AVM2ParseException("Cannot compile native code", lexer.yyline());
} else if (s.group == SymbolGroup.IDENTIFIER) {
customNs = s.value.toString();
} else {
if (namespace != null) {
throw new AVM2ParseException("Only one access identifier allowed", lexer.yyline());
}
} else if (namespace != null) {
throw new AVM2ParseException("Only one access identifier allowed", lexer.yyline());
}
switch (s.type) {
case PUBLIC:
@@ -1378,16 +1375,14 @@ public class ActionScript3Parser {
ParsedSymbol sx = lex();
if (sx.group != SymbolGroup.IDENTIFIER) {
lexer.pushback(sx);
} else if (!sx.value.equals("xml")) {
lexer.pushback(sx);
} else {
if (!sx.value.equals("xml")) {
lexer.pushback(sx);
} else {
expectedType(SymbolType.NAMESPACE);
expectedType(SymbolType.ASSIGN);
GraphTargetItem ns = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables);
ret = new DefaultXMLNamespace(null, null, ns);
//TODO: use dxns for attribute namespaces instead of dxnslate
}
expectedType(SymbolType.NAMESPACE);
expectedType(SymbolType.ASSIGN);
GraphTargetItem ns = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables);
ret = new DefaultXMLNamespace(null, null, ns);
//TODO: use dxns for attribute namespaces instead of dxnslate
}
}
if (ret == null) {
@@ -1557,10 +1552,8 @@ public class ActionScript3Parser {
if (firstCommand instanceof InAVM2Item) {
forin = true;
inexpr = (InAVM2Item) firstCommand;
} else {
if (forin) {
throw new AVM2ParseException("In expression required", lexer.yyline());
}
} else if (forin) {
throw new AVM2ParseException("In expression required", lexer.yyline());
}
Loop floop = new Loop(uniqId(), null, null);
@@ -1910,10 +1903,8 @@ public class ActionScript3Parser {
lexer.pushback(s);
if (cmd == null) {
expr.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables));
} else {
if (!cmd.hasReturnValue()) {
throw new AVM2ParseException("Expression expected", lexer.yyline());
}
} else if (!cmd.hasReturnValue()) {
throw new AVM2ParseException("Expression expected", lexer.yyline());
}
return new CommaExpressionItem(null, null, expr);
}
@@ -1932,17 +1923,42 @@ public class ActionScript3Parser {
*
* @param symb
*/
private void xmlToGreaterFix(ParsedSymbol symb) {
private void xmlToLowerThanFix(ParsedSymbol symb) {
if (symb.isType(SymbolType.XML_STARTVARTAG_BEGIN, SymbolType.XML_STARTTAG_BEGIN)) {
lexer.yypushbackstr(symb.value.toString().substring(1)); //parse again as GREATER_THAN
symb.type = SymbolType.GREATER_THAN;
lexer.yypushbackstr(symb.value.toString().substring(1)); //parse again as LOWER_THAN
String pb = symb.value.toString().substring(1);
symb.type = SymbolType.LOWER_THAN;
symb.group = SymbolGroup.OPERATOR;
symb.value = "<";
if (pb.charAt(0) == '=') {
symb.type = SymbolType.LOWER_EQUAL;
symb.value = "<=";
pb = pb.substring(1);
}
lexer.yypushbackstr(pb); //parse again as LOWER_THAN
}
}
private void regexpToDivideFix(ParsedSymbol symb) {
if (symb.isType(SymbolType.REGEXP)) {
String pb = symb.value.toString().substring(1);
symb.type = SymbolType.DIVIDE;
symb.group = SymbolGroup.OPERATOR;
symb.value = "/";
if (pb.charAt(0) == '=') {
symb.type = SymbolType.ASSIGN_DIVIDE;
symb.value = "/=";
pb = pb.substring(1);
}
lexer.yypushbackstr(pb); //parse again as DIVIDE
}
}
private ParsedSymbol peekExprToken() throws IOException, AVM2ParseException {
ParsedSymbol lookahead = lex();
xmlToGreaterFix(lookahead);
xmlToLowerThanFix(lookahead);
regexpToDivideFix(lookahead);
lexer.pushback(lookahead);
return lookahead;
@@ -2169,6 +2185,17 @@ public class ActionScript3Parser {
ParsedSymbol s = lex();
boolean allowMemberOrCall = false;
switch (s.type) {
case REGEXP:
String p = (String) s.value;
p = p.substring(1);
int spos = p.lastIndexOf("/");
String mod = p.substring(spos + 1);
p = p.substring(0, spos);
p = p.replace("\\/", "/");
ret = new RegExpAvm2Item(p, mod, null, null);
allowMemberOrCall = true;
break;
case XML_STARTTAG_BEGIN:
lexer.pushback(s);
ret = xml(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables);
@@ -2430,13 +2457,11 @@ public class ActionScript3Parser {
if (isStar) {
openedNamespaces.add(new NamespaceItem(fullName, Namespace.KIND_PACKAGE));
} else if (isUse) {
//Note: in this case, fullName attribute will be changed to real NS insude NamespaceItem
openedNamespaces.add(new NamespaceItem(fullName, Namespace.KIND_NAMESPACE));
} else {
if (isUse) {
//Note: in this case, fullName attribute will be changed to real NS insude NamespaceItem
openedNamespaces.add(new NamespaceItem(fullName, Namespace.KIND_NAMESPACE));
} else {
importedClasses.add(fullName);
}
importedClasses.add(fullName);
}
expected(s, lexer.yyline(), SymbolType.SEMICOLON);

View File

@@ -34,5 +34,6 @@ public enum SymbolGroup {
EOF,
//GLOBALFUNC,
GLOBALCONST,
PREPROCESSOR
PREPROCESSOR,
REGEXP
}

View File

@@ -206,7 +206,8 @@ public enum SymbolType {
FILTER(GraphTargetItem.PRECEDENCE_PRIMARY, false),
DESCENDANTS(GraphTargetItem.PRECEDENCE_PRIMARY, false),
NATIVE,
PREPROCESSOR(GraphTargetItem.PRECEDENCE_PRIMARY, false);
PREPROCESSOR(GraphTargetItem.PRECEDENCE_PRIMARY, false),
REGEXP(GraphTargetItem.PRECEDENCE_PRIMARY, false);
private int precedence = GraphTargetItem.NOPRECEDENCE;

View File

@@ -553,6 +553,14 @@ public class Configuration {
@ConfigurationName("internalFlashViewer.execute.as12")
public static final ConfigurationItem<Boolean> internalFlashViewerExecuteAs12 = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("script")
public static final ConfigurationItem<Boolean> displayDupInstructions = null;
@ConfigurationDefaultBoolean(true)
@ConfigurationCategory("script")
public static final ConfigurationItem<Boolean> useRegExprLiteral = null;
private enum OSId {
WINDOWS, OSX, UNIX

View File

@@ -17,6 +17,7 @@
package com.jpexs.decompiler.graph.model;
import com.jpexs.decompiler.flash.SourceGeneratorLocalData;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.graph.CompilationException;
import com.jpexs.decompiler.graph.GraphSourceItem;
@@ -49,7 +50,7 @@ public class DuplicateItem extends GraphTargetItem implements SimpleValue {
@Override
public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException {
if ((value instanceof SimpleValue) && (((SimpleValue) value).isSimpleValue())) {
if (((value instanceof SimpleValue) && (((SimpleValue) value).isSimpleValue())) || !Configuration.displayDupInstructions.get()) {
return value.appendTo(writer, localData);
}
writer.append("§§dup(");