/*
* StatementBlockImpl.java
* @Author Oleg Gorobets
* Created: 24.07.2007
* CVS-ID: $Id:
*************************************************************************/
package org.swfparser;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.springframework.util.StringUtils;
import org.apache.log4j.Logger;
import org.swfparser.annotations.NewAnalyzer;
import org.swfparser.exception.StatementBlockException;
import org.swfparser.operation.*;
import org.swfparser.operation.StoreRegisterOperation.RegisterHandle;
import org.swfparser.pattern.BreakPattern;
import org.swfparser.pattern.ContinuePattern;
import org.swfparser.pattern.DoWhilePattern;
import org.swfparser.pattern.ForInPattern;
import org.swfparser.pattern.IfElsePattern;
import org.swfparser.pattern.IfPattern;
import org.swfparser.pattern.Pattern;
import org.swfparser.pattern.SkipForDoWhilePattern;
import org.swfparser.pattern.SkipPattern;
import org.swfparser.pattern.SwitchPattern;
import org.swfparser.pattern.TellTargetPattern;
import org.swfparser.pattern.WhilePattern;
import org.swfparser.util.PrintfFormat;
import com.jswiff.io.OutputBitStream;
import com.jswiff.swfrecords.actions.Action;
import com.jswiff.swfrecords.actions.ActionConstants;
import com.jswiff.swfrecords.actions.Branch;
import com.jswiff.swfrecords.actions.ConstantPool;
import com.jswiff.swfrecords.actions.DefineFunction;
import com.jswiff.swfrecords.actions.DefineFunction2;
import com.jswiff.swfrecords.actions.GetURL2;
import com.jswiff.swfrecords.actions.GoToFrame;
import com.jswiff.swfrecords.actions.GoToFrame2;
import com.jswiff.swfrecords.actions.GoToLabel;
import com.jswiff.swfrecords.actions.NullStackValue;
import com.jswiff.swfrecords.actions.Pop;
import com.jswiff.swfrecords.actions.Push;
import com.jswiff.swfrecords.actions.SetTarget;
import com.jswiff.swfrecords.actions.SetTarget2;
import com.jswiff.swfrecords.actions.StackValue;
import com.jswiff.swfrecords.actions.StoreRegister;
import com.jswiff.swfrecords.actions.Try;
import com.jswiff.swfrecords.actions.UndefinedStackValue;
import com.jswiff.swfrecords.actions.WaitForFrame;
import com.jswiff.swfrecords.actions.WaitForFrame2;
import com.jswiff.swfrecords.actions.With;
public class StatementBlockImpl implements StatementBlock {
private static Logger logger = Logger.getLogger(StatementBlockImpl.class);
private List<Operation> statements = new ArrayList<Operation>();
private static PrintfFormat actionFormat = new PrintfFormat("A:0x%02X (%s) label:%s");
private ExecutionContext context;
private StatementBlockMoment moment = new StatementBlockMoment();
private boolean canAddStatements = true;
public void read(List<Action> actions) throws StatementBlockException {
boolean isRootBlock = context.getOperationStack().isEmpty();
Operation enclosingOperation=null;
String blockName;
moment.setActions(actions);
boolean newLabelsWereBuilt = false;
if (isRootBlock) {
blockName = "$ (rootMovie)" ;
newLabelsWereBuilt=true;
// context.setPatternAnalyzer(new PatternAnalyzer(actions));
PatternAnalyzerEx patternAnalyzerEx = new PatternAnalyzerEx(new PatternContext(), actions);
patternAnalyzerEx.analyze();
context.setPatternAnalyzerEx(patternAnalyzerEx);
} else {
enclosingOperation = context.getOperationStack().peek();
blockName = enclosingOperation.getClass().getSimpleName();
if (enclosingOperation.getClass().getAnnotation(NewAnalyzer.class)!=null) {
// context.setPatternAnalyzer(new PatternAnalyzer(actions));
PatternAnalyzerEx patternAnalyzerEx = new PatternAnalyzerEx(new PatternContext(), actions);
patternAnalyzerEx.analyze();
context.setPatternAnalyzerEx(patternAnalyzerEx);
newLabelsWereBuilt=true;
}
}
// read all labels
logger.debug(" : : : BLOCK START : : : - blockName: " + blockName+", actions.size = "+actions.size());//+", Stack size : " + stack.size());
String regInfo="REGS:";
String labelInfo = newLabelsWereBuilt ? "LABELS BUILT: " : "LABELS INHERITED: ";
int yui=1;
for (Operation op : context.getRegisters()) {
regInfo += (yui++) + " => " + op + ",";
}
if (context.getPatternAnalyzerEx()!=null) {
for (String lab : context.getPatternAnalyzerEx().getLabels().keySet()) {
labelInfo += lab + ", ";
}
}
logger.debug(regInfo);
logger.debug(labelInfo);
// for (Action action : actions) {
//
// if (action instanceof Branch) {
// Branch branch = (Branch) action;
// logger.debug(
// actionFormat.sprintf(new Object[]{action.getCode(),ActionConstants.getActionName(action.getCode())}) +
// " L:"+action.getLabel()+" BL:"+branch.getBranchLabel()
// );
// } else {
// logger.debug(
// actionFormat.sprintf(new Object[]{action.getCode(),ActionConstants.getActionName(action.getCode())}) +
// " L:"+action.getLabel()
// );
// }
// }
// actionIndex = actions.size();
int actionIndex = 0;
Map<Operation, Action> stackOperationToActionMap = new IdentityHashMap<Operation, Action>();
try {
while (actionIndex < actions.size()) {
Action action = actions.get(actionIndex);
// set context
moment.setActionIndex(actionIndex);
moment.setStatements(statements);
context.getMomentStack().push(moment);
String label = action.getLabel();
logger.debug(
"=== " +
ActionConstants.getActionName(action.getCode()) + " === " +
(label != null ? label : " (label=null)"));
int actionIndexShift=1;
if (context.getPatternAnalyzerEx()!=null && context.getPatternAnalyzerEx().getPatternByLabel(action.getLabel())!=null) {
Pattern branchPattern = context.getPatternAnalyzerEx().getPatternByLabel(action.getLabel());
logger.debug("Branch pattern found: "+branchPattern);
handleByPattern(actionIndex, action);
actionIndexShift += branchPattern.size();
} else {
actionIndexShift += handleByActionCode(action);
}
// map newly added operations to this action
for (Operation operation : context.getExecStack()) {
if (!stackOperationToActionMap.containsKey(operation)) {
stackOperationToActionMap.put(operation, action);
}
}
context.getMomentStack().pop();
actionIndex += actionIndexShift;
logger.debug("STACK, length = "+context.getExecStack().size() + ", values=" + context.getExecStack());
}
} catch (EmptyStackException e) {
e.printStackTrace();
Stack<Operation> operationStack = context.getOperationStack();
while (!operationStack.isEmpty()) {
logger.debug("OP_STACK_TRACE: "+operationStack.pop());
}
logger.debug("~~~~~ Writing UNFINISHED BLOCK statements ~~~");
for (Operation op : statements) {
String endOfStatement = CodeUtil.endOfStatement(op);
logger.debug(op.getStringValue(0)+endOfStatement+" // "+op+"\n");
}
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
OutputBitStream outputBitStream = new OutputBitStream(byteArrayOutputStream);
for (Action a : actions) {
a.write(outputBitStream);
}
statements.clear();
ByteCodeOperation byteCodeOperation = new ByteCodeOperation(byteArrayOutputStream.toByteArray());
statements.add(byteCodeOperation);
logger.debug("Adding bytecode operation\n"+byteCodeOperation.getStringValue(0));
} catch (IOException e1) {
e1.printStackTrace();
}
throw new StatementBlockException(e);
} catch (StatementBlockException e) {
e.printStackTrace();
Stack<Operation> operationStack = context.getOperationStack();
while (!operationStack.isEmpty()) {
logger.debug("OP_STACK_TRACE: "+operationStack.pop());
}
logger.debug("~~~~~ Writing UNFINISHED BLOCK statements ~~~");
for (Operation op : statements) {
String endOfStatement = CodeUtil.endOfStatement(op);
logger.debug(op.getStringValue(0)+endOfStatement+" // "+op.getClass().getName()+"\n");
}
throw new StatementBlockException(e);
}
postProcessStatements();
if (isRootBlock) {
checkStackInTheEndOfTheBlock(stackOperationToActionMap);
logger.debug("~~~~~ Writing statements ~~~");
for (Operation op : statements) {
String endOfStatement = CodeUtil.endOfStatement(op);
logger.debug(op.getStringValue(0)+endOfStatement+" // "+op.getClass().getName()+"\n");
}
}
logger.debug(" : : : BLOCK FINISHED : : : - blockName: " + blockName);
}
protected void checkStackInTheEndOfTheBlock(Map<Operation, Action> stackOperationToActionMap) {
logger.debug("Checking stack before writing statements...");
Stack<Operation> stack = context.getExecStack();
if (stack.isEmpty()) {
logger.debug("STACK is empty ... OK");
} else {
logger.debug("STACK length: " + stack.size());
for (Operation operation : stack) {
logger.debug("Checking "+operation+". Maps to action "+stackOperationToActionMap.get(operation));
}
}
}
private void handleByPattern(int actionIndex, Action action) throws StatementBlockException {
// Pattern branchPattern = context.getPatternAnalyzer().getPatternByLabel(action.getLabel());
Pattern branchPattern = context.getPatternAnalyzerEx().getPatternByLabel(action.getLabel());
Class branchPatternClass = branchPattern.getClass();
Stack<Operation> stack = context.getExecStack();
if (branchPatternClass.equals( SwitchPattern.class )) {
addStatement( new SwitchOperation(context, (SwitchPattern) branchPattern));
}
if (branchPatternClass.equals( TellTargetPattern.class )) {
if (action instanceof SetTarget) {
SetTarget setTarget = (SetTarget) action;
addStatement( new SetTargetOperation(stack,context,((TellTargetPattern)branchPattern).getActions(),setTarget));
} else if (action instanceof SetTarget2) {
SetTarget2 setTarget = (SetTarget2) action;
addStatement( new SetTarget2Operation(stack,context,((TellTargetPattern)branchPattern).getActions(),setTarget));
}
return;
}
if (branchPatternClass.equals( WhilePattern.class )) {
Operation op = new WhileOperation(stack,((WhilePattern)branchPattern).getActions(),context);
addStatement(op);
return;
}
if (branchPatternClass.equals( DoWhilePattern.class )) {
canAddStatements = true;
Operation op = new DoWhileOperation(stack,((DoWhilePattern)branchPattern).getActions(),context);
logger.debug("Adding "+op+" to statement");
addStatement(op);
return;
}
if (branchPatternClass.equals( IfElsePattern.class )) {
Operation op = new IfElseOperation(stack,context,((IfElsePattern)branchPattern).getIfActions(),((IfElsePattern)branchPattern).getElseActions());
addStatement(op);
return;
}
if (branchPatternClass.equals( ContinuePattern.class )) {
addStatement(new SimpleOperation("continue"));
return;
}
if (branchPatternClass.equals( BreakPattern.class )) {
addStatement(new SimpleOperation("break"));
return;
}
// should go before SkipPattern
if (branchPatternClass.equals( SkipForDoWhilePattern.class )) {
canAddStatements = false;
// context.getPatternAnalyzer().clearBranchPattern(action.getLabel());
context.getPatternAnalyzerEx().clearBranchPattern(action.getLabel());
handleByActionCode(action);
return;
}
if (branchPatternClass.equals( SkipPattern.class )) {
return;
}
if (branchPatternClass.equals( ForInPattern.class )) {
addStatement(new ForInOperation(context,((ForInPattern)branchPattern).getActions(),((ForInPattern)branchPattern).getVarActions()));
return;
}
//
// If pattern should go the last!!!
//
if (branchPatternClass.equals( IfPattern.class )) {
Operation op = new IfOperation(stack,((IfPattern)branchPattern).getActions(),context);
addStatement(op);
return;
}
}
private int handleByActionCode(Action action) throws StatementBlockException {
int additionalActionShift = 0;
Stack<Operation> stack = context.getExecStack();
Operation op;
// int actionIndexShift = 1; // default
switch (action.getCode()) {
case ActionConstants.CONSTANT_POOL:
ConstantPool constantPool = (ConstantPool)action;
context.getConstants().addAll(constantPool.getConstants());
logger.debug("Loaded constants, length: " + context.getConstants().size() + ", values: " + context.getConstants());
break;
case ActionConstants.PUSH :
handlePush((Push)action, stack);
break;
case ActionConstants.PUSH_DUPLICATE :
stack.push(stack.peek());
break;
case ActionConstants.POP :
if (stack.isEmpty()) {
logger.error("Empty stack and POP() found");
break;
} else {
boolean writePop = stack.peek() instanceof DualUse;
if (writePop && !statements.isEmpty()) {
// check last statement
// if it is StoreRegister than do NOT write this POP()
Operation lastStatement = statements.get(statements.size()-1);
if (lastStatement instanceof StoreRegisterOperation) {
// writePop = lastStatement.getClass().getAnnotation(DoNotWritePop.class) == null;
writePop = ((StoreRegisterOperation) lastStatement).getOp() != ((ExecutionStack) stack).peek();
}
}
if (writePop) {
logger.debug("Writing POP()");
((DualUse)stack.peek()).markAsStatement();
addStatement(stack.pop());
} else {
logger.debug("Skipping POP()");
stack.pop();
}
}
break;
case ActionConstants.STACK_SWAP:
Operation item1 = stack.pop();
Operation item2 = stack.pop();
stack.push(item1);
stack.push(item2);
break;
case ActionConstants.DEFINE_LOCAL :
op = new DefineLocalOperation(stack);
addStatement(op);
break;
case ActionConstants.DEFINE_LOCAL_2 :
op = new DefineLocal2Operation(stack);
addStatement(op);
break;
//
// TODO: group simple operations
//
case ActionConstants.PREVIOUS_FRAME :
addStatement(new SimpleOperation("prevFrame()"));
break;
case ActionConstants.NEXT_FRAME:
stack.push(new SimpleOperation("nextFrame()"));
break;
case ActionConstants.PLAY:
if (!statements.isEmpty() && (statements.get(statements.size()-1) instanceof ActionAware)) {
((ActionAware)statements.get(statements.size()-1)).setAction(GotoFrameOperation.ACTION_PLAY);
} else {
addStatement(new SimpleOperation("play()"));
}
break;
case ActionConstants.STOP :
addStatement(new SimpleOperation("stop()"));
break;
case ActionConstants.STOP_SOUNDS:
addStatement(new SimpleOperation("stopAllSounds()"));
break;
case ActionConstants.GET_TIME:
stack.push(new SimpleFunctionOperation("getTimer()"));
break;
case ActionConstants.THROW:
addStatement(new ThrowOperation(stack));
break;
//
// Arithmetic operations
//
case ActionConstants.ADD:
case ActionConstants.ADD_2:
stack.push(new AddOperation(stack,action));
break;
case ActionConstants.SUBTRACT:
stack.push(new SubtractOperation(stack,action));
break;
case ActionConstants.DIVIDE:
stack.push(new DivideOperation(stack,action));
break;
case ActionConstants.MULTIPLY:
stack.push(new MultiplyOperation(stack,action));
break;
case ActionConstants.MODULO:
op = new ModuloOperation(stack,action);
stack.push(op);
break;
case ActionConstants.INCREMENT:
op = new SimpleIncrementOperation(stack);
stack.push(op);
break;
case ActionConstants.DECREMENT:
op = new SimpleDecrementOperation(stack);
stack.push(op);
break;
case ActionConstants.STRING_ADD:
stack.push(new StringAddOperation(stack));
break;
//
// Boolean operations
//
case ActionConstants.LESS:
case ActionConstants.LESS_2:
op = new LessOperation(stack);
stack.push(op);
break;
case ActionConstants.EQUALS:
case ActionConstants.EQUALS_2:
stack.push(new EqualsOperation(stack));
break;
case ActionConstants.STRICT_EQUALS:
stack.push(new StrictEqualsOperation(stack));
break;
case ActionConstants.GREATER:
op = new GreaterOperation(stack);
stack.push(op);
break;
case ActionConstants.AND:
stack.push(new AndOperation(stack));
break;
case ActionConstants.OR:
stack.push(new OrOperation(stack));
break;
case ActionConstants.GET_VARIABLE:
op = new GetVariableOperation(stack);
stack.push(op);
break;
case ActionConstants.CALL_METHOD:
stack.push(new CallMethodOperation(stack));
break;
case ActionConstants.SET_MEMBER:
op = new SetMemberOperation(stack);
addStatement(op);
break;
case ActionConstants.DEFINE_FUNCTION:
DefineFunction defineFunction = (DefineFunction)action;
op = new DefineFunctionOperation(stack,context,defineFunction);
if (StringUtils.hasText(defineFunction.getName())) {
addStatement(op); // function as statement
} else {
stack.push(op); // function as operation, put it to stack
}
break;
case ActionConstants.DEFINE_FUNCTION_2:
DefineFunction2 defineFunction2 = (DefineFunction2)action;
op = new DefineFunction2Operation(stack,defineFunction2,context);
if (StringUtils.hasText(defineFunction2.getName())) {
addStatement(op); // function as statement
} else {
stack.push(op); // function as operation, put it to stack
}
break;
case ActionConstants.GET_MEMBER:
stack.push( new GetMemberOperation(stack) );
break;
case ActionConstants.GET_PROPERTY:
stack.push( new GetPropertyOperation(stack) );
break;
//
// Type convertion funcs
//
case ActionConstants.TO_INTEGER:
stack.push(new ToIntegerOperation(stack));
break;
case ActionConstants.TO_NUMBER:
stack.push(new ToNumberOperation(stack));
break;
case ActionConstants.TO_STRING:
stack.push(new ToStringOperation(stack));
break;
// TODO: Check LABEL_OUT
case ActionConstants.JUMP:
case ActionConstants.IF:
Branch branch = (Branch) action;
throw new StatementBlockException("Should be handled by pattern: "+action);
case ActionConstants.DELETE:
addStatement(new DeleteOperation(stack));
stack.push(new TrueOperation()); // TODO should be the result of the DeleteOperation?
break;
case ActionConstants.DELETE_2:
addStatement(new Delete2Operation(stack));
break;
case ActionConstants.NOT:
op = new NotOperation(stack);
// op = NotOperation.createNotOperation(stack);
stack.push(op);
break;
case ActionConstants.GET_URL:
op = new GetURLOperation(action);
addStatement(op); // ???
// finishThisBlock = true;
break;
case ActionConstants.GET_URL_2:
op = new GetURL2Operation(stack,(GetURL2)action);
addStatement(op); // ???
// finishThisBlock = true;
break;
case ActionConstants.SET_VARIABLE:
op = new SetVariableOperation(context);
addStatement(op);
break;
case ActionConstants.STORE_REGISTER:
op = new StoreRegisterOperation(context, ( StoreRegister ) action);
addStatement(op);
// Push the variable into the register instead of the code.
// This used to break constructor definitions, where `__R1 = function(){}` is the constructor
// and `function(){}` had been pushed into the register, instead of `__R1`.
if (stack.peek() instanceof DefineFunction2Operation || stack.peek() instanceof DefineFunctionOperation) { // This might also be valid for many other cases, but lets be as narrow as possible.
stack.pop();
StackValue stackValue = new StackValue();
stackValue.setRegisterNumber( (short) ((StoreRegisterOperation) op).getRegisterNumber());
Push pushAction = new Push();
pushAction.addValue(stackValue);
handlePush(pushAction, stack);
}
break;
case ActionConstants.INIT_ARRAY:
stack.push(new InitArrayOperation(stack));
break;
case ActionConstants.INIT_OBJECT:
stack.push(new InitObjectOperation(stack));
break;
case ActionConstants.NEW_OBJECT:
stack.push( new NewObjectOperation(context) );
break;
case ActionConstants.NEW_METHOD:
stack.push( new NewMethodOperation(context) );
break;
case ActionConstants.CALL_FUNCTION:
op = new CallFunctionOperation(stack);
stack.push(op);
break;
case ActionConstants.GO_TO_FRAME:
op = new GotoFrameOperation((GoToFrame)action);
// stack.push(op);
addStatement(op);
break;
case ActionConstants.GO_TO_FRAME_2:
op = new GotoFrame2Operation(stack,(GoToFrame2)action);
// stack.push(op);
addStatement(op);
break;
case ActionConstants.GO_TO_LABEL:
addStatement(new GotoLabelOperation(stack,(GoToLabel)action));
break;
case ActionConstants.TRACE:
addStatement( new TraceOperation(stack));
break;
case ActionConstants.RANDOM_NUMBER:
op = new RandomOperation(stack);
stack.push(op);
break;
case ActionConstants.REMOVE_SPRITE:
addStatement(new RemoveMovieClipOperation(stack));
break;
case ActionConstants.RETURN:
addStatement( new ReturnOperation(stack));
break;
case ActionConstants.SET_PROPERTY:
addStatement( new SetPropertyOperation(stack));
break;
case ActionConstants.START_DRAG:
addStatement(new StartDragOperation(stack));
break;
case ActionConstants.END_DRAG:
addStatement(new SimpleOperation("stopDrag()"));
break;
case ActionConstants.STRING_GREATER:
stack.push(new GreaterOperation(stack));
break;
case ActionConstants.STRING_LESS:
stack.push(new LessOperation(stack));
break;
/*
ActionTargetPath
If the object in the stack is of type MovieClip, the object's target path is pushed on the stack
in dot notation. If the object is not a MovieClip, the result is undefined rather than the
movie clip target path.
ActionTargetPath does the following:
1. Pops the object off the stack.
2. Pushes the target path onto the stack.
*/
case ActionConstants.TARGET_PATH:
stack.push( new TargetPathOperation(stack) );
break;
case ActionConstants.TYPE_OF:
stack.push(new TypeOfOperation(stack));
break;
case ActionConstants.WITH:
addStatement( new WithOperation(stack,context,(With) action));
break;
case ActionConstants.TRY:
addStatement( new TryCatchOperation(stack,context,(Try) action));
break;
case ActionConstants.CAST_OP:
stack.push( new CastOperation(stack) );
break;
case ActionConstants.CLONE_SPRITE:
addStatement( new CloneSpriteOperation(stack));
break;
case ActionConstants.ENUMERATE:
case ActionConstants.ENUMERATE_2:
throw new StatementBlockException("ENUMERATE should be handled by pattern: "+action);
case ActionConstants.EXTENDS:
addStatement( new ExtendsOperation(stack));
break;
case ActionConstants.IMPLEMENTS_OP:
addStatement( new ImplementsOperation(stack));
break;
case ActionConstants.INSTANCE_OF:
stack.push( new InstanceOfOperation(stack) );
break;
//
// Bitwise operations
//
case ActionConstants.BIT_AND:
stack.push( new BitwiseAndOperation(stack) );
break;
case ActionConstants.BIT_OR:
stack.push( new BitwiseOrOperation(stack) );
break;
case ActionConstants.BIT_L_SHIFT:
stack.push( new BitwiseLShiftOperation(stack) );
break;
case ActionConstants.BIT_R_SHIFT:
stack.push( new BitwiseRShiftOperation(stack) );
break;
case ActionConstants.BIT_XOR:
stack.push( new BitwiseXorOperation(stack) );
break;
case ActionConstants.BIT_U_R_SHIFT:
stack.push( new BitwiseURShiftOperation(stack) );
break;
//
// Deprecated since SWF 5
//
case ActionConstants.ASCII_TO_CHAR:
stack.push(new ChrOperation(stack));
break;
case ActionConstants.CHAR_TO_ASCII:
stack.push(new OrdOperation(stack));
break;
case ActionConstants.M_B_ASCII_TO_CHAR:
stack.push(new MbChrOperation(stack));
break;
case ActionConstants.M_B_CHAR_TO_ASCII:
stack.push(new MbOrdOperation(stack));
break;
case ActionConstants.M_B_STRING_EXTRACT:
stack.push(new MbSubstringOperation(stack));
break;
case ActionConstants.M_B_STRING_LENGTH:
stack.push(new MbLengthOperation(stack));
break;
case ActionConstants.SET_TARGET:
case ActionConstants.SET_TARGET_2:
throw new StatementBlockException("Should be handled by pattern: "+action);
// SetTarget setTarget = (SetTarget) action;
// addStatement( new SetTargetOperation(stack,context,setTarget) );
// break;
// SetTarget2 setTarget2 = (SetTarget2) action;
// addStatement( new SetTarget2Operation(stack,context,setTarget2) );
// break;
case ActionConstants.STRING_EQUALS:
stack.push(new EqualsOperation(stack));
break;
case ActionConstants.STRING_EXTRACT:
stack.push(new StringExtractOperation(stack));
break;
case ActionConstants.STRING_LENGTH:
stack.push(new StringLengthOperation(stack));
break;
case ActionConstants.TOGGLE_QUALITY:
addStatement(new SimpleOperation("toggleHighQuality()"));
break;
case ActionConstants.WAIT_FOR_FRAME:
WaitForFrame waitForFrame = (WaitForFrame) action;
additionalActionShift = waitForFrame.getSkipCount();
List<Action> skipActions = new ArrayList<Action>(additionalActionShift);
int actionIndex = moment.getActionIndex();
for (int j=1;j<=additionalActionShift;j++) {
skipActions.add(moment.getActions().get(actionIndex+j));
}
addStatement( new WaitForFrameOperation(stack,context,waitForFrame,skipActions));
break;
case ActionConstants.WAIT_FOR_FRAME_2:
WaitForFrame2 waitForFrame2 = (WaitForFrame2) action;
additionalActionShift = waitForFrame2.getSkipCount();
skipActions = new ArrayList<Action>(additionalActionShift);
actionIndex = moment.getActionIndex();
for (int j=1;j<=additionalActionShift;j++) {
skipActions.add(moment.getActions().get(actionIndex+j));
}
addStatement( new WaitForFrame2Operation(stack,context,waitForFrame2,skipActions));
break;
case ActionConstants.CALL:
addStatement( new CallOperation(stack));
break;
case ActionConstants.END:
// just skip this action
break;
default:
logger.error("UNSUPPORTED ACTION " + action.getCode());
}
return additionalActionShift;
}
protected void addStatement(Operation op) {
if (canAddStatements) {
if (!(op instanceof SkipOperation) || (op instanceof SkipOperation && !((SkipOperation)op).skip())) {
if (op instanceof OperationFactory) {
op = ((OperationFactory)op).getObject();
}
statements.add(op);
postProcessAfterStatement();
}
}
}
private static int modifiedVarIndex = 1;
private static int modifiedRegIndex = 1000;
protected void postProcessAfterStatement() {
Operation statement = statements.get(statements.size()-1);
logger.debug("Checking stack after "+statement);
Stack<Operation> stack = context.getExecStack();
logger.debug("Stack size is "+stack.size());
// First try handle ++x
if (handlePlusPlusX(statement,stack)) {
return;
}
if (stack.isEmpty()) {
return;
}
if (statement instanceof AssignOperation) {
Operation leftOp = ((AssignOperation)statement).getLeftPart();
while (!leftOp.getOperations().isEmpty()) {
leftOp = leftOp.getOperations().get(0);
}
// Try handle x++
if (handleXPlusPlus(statement,stack)) {
return;
}
// Check strings
if (leftOp instanceof StackValue && StackValue.TYPE_STRING == ((StackValue)leftOp).getType()) {
// Retrieve variable name
String variableName = ((StackValue)leftOp).getString();
for (Operation operation : stack) {
// Check if there is use of GetVariable(variableName) in the stack
Operation operationToFind = new GetVariableOperation(new StackValue(variableName));
List<Operation> operationsToCheck = getAllUnderlyingOperations(operation);
boolean stackContainsAssignmentVariable = false;
List<Operation> operationsToChange = new ArrayList<Operation>();
for (Operation underOp : operationsToCheck) {
if (operationToFind.equals(underOp)) {
logger.debug("The stack contains "+operation+" which itself contains "+underOp+". Fixing it...");
stackContainsAssignmentVariable = true;
operationsToChange.add(underOp);
}
}
if (stackContainsAssignmentVariable) {
/*
* x in stack
* change to:
* 1) x__m = x;
* 2) push getVariable(x__m)
*
*/
String newVariableName = variableName+"__m_"+modifiedVarIndex++;
for (Operation chOp : operationsToChange) {
logger.debug("Changing the name of variable "+variableName+" to "+newVariableName);
((GetVariableOperation)chOp).setOp(new StackValue(newVariableName));
}
statements.add(statements.size()-1, new DefineLocalOperation(new StackValue(newVariableName),new GetVariableOperation(new StackValue(variableName))));
}
}
}
// Check registers
if (leftOp instanceof RegisterHandle) {
RegisterHandle registerHandle = (RegisterHandle) leftOp;
int startStackIdx = stack.size()-1;
// get next action
int thisActionIndex = context.getMomentStack().peek().getActionIndex();
int nextActionIndex = thisActionIndex + 1;
if (nextActionIndex < context.getMomentStack().peek().getActions().size()) {
Action nextAction = context.getMomentStack().peek().getActions().get(nextActionIndex);
if (nextAction instanceof Pop) {
// do not check the top of stack as this value will be discarded
startStackIdx--;
}
}
if (startStackIdx == stack.size()-1) {
Action thisAction = context.getMomentStack().peek().getActions().get(thisActionIndex);
if (thisAction instanceof StoreRegister) {
startStackIdx--;
}
}
// find this register handle in stack
for (int stackIdx = startStackIdx; stackIdx>=0; stackIdx--) {
Operation operation = stack.get(stackIdx);
logger.debug("STACK_VAL = "+operation);
List<Operation> operationsToCheck = getAllUnderlyingOperations(operation);
List<Operation> operationsToChange = new ArrayList<Operation>();
boolean stackContainsAssignmentVariable = false;
for (Operation underOp : operationsToCheck) {
if (registerHandle.equals(underOp)) {
logger.debug("The stack contains "+operation+" which itself contains "+underOp+". Fixing it...");
stackContainsAssignmentVariable = true;
operationsToChange.add(underOp);
}
}
if (stackContainsAssignmentVariable) {
/*
* __Rn in stack
* change to:
* 1) __R__m = x;
* 2) push getVariable(x__m)
*
*/
int oldRegisterNumber = registerHandle.getRegisterNumber();
int newRegisterNumber = modifiedRegIndex++;
for (Operation chOp : operationsToChange) {
logger.debug("Changing name of register variable "+oldRegisterNumber+" to "+newRegisterNumber);
((RegisterHandle)chOp).setRegisterNumber(newRegisterNumber);
}
statements.add(statements.size()-1, new DefineLocalOperation(new RegisterHandle(newRegisterNumber),new RegisterHandle(oldRegisterNumber)));
}
}
}
}
}
private boolean handlePlusPlusX(Operation statement, Stack<Operation> stack) {
statement = getRealStatement(statement);
// get all operations except stack top
List<Operation> allStackOperations = new ArrayList<>();
if (!stack.isEmpty()) {
for (int i = 0; i<stack.size()-1; i++) {
allStackOperations.addAll(getAllUnderlyingOperations(stack.get(i)));
}
}
// boolean inc = statement instanceof PostIncrementOperation;
// boolean dec = statement instanceof PostDecrementOperation;
if (!(statement instanceof StoreRegisterOperation)) {
return false;
}
Operation insideOp = ((StoreRegisterOperation) statement).getOp();
boolean inc = insideOp instanceof SimpleIncrementOperation;
boolean dec = insideOp instanceof SimpleDecrementOperation;
if (inc || dec) {
Operation incOperation = ((UnaryOperation)insideOp).getOp();
if (stack.isEmpty() || !allStackOperations.contains(incOperation)) {
List<Operation> registerOperations = context.getRegisters();
int lastRegisterOperationIndex = registerOperations.size() - 1;
if (!registerOperations.isEmpty() && registerOperations.get(lastRegisterOperationIndex) instanceof RegisterHandle) {
RegisterHandle registerHandle = (RegisterHandle) registerOperations.get(lastRegisterOperationIndex);
if (registerHandle.getUndelrlyingOp() instanceof SimpleIncrementOperation || registerHandle.getUndelrlyingOp() instanceof SimpleDecrementOperation) {
// SimpleIncrementOperation simpleIncrementOperation = (SimpleIncrementOperation) registerHandle.getUndelrlyingOp();
// if (incOperation.equals(simpleIncrementOperation.getOp())) {
// we might need to check this too
logger.debug("Simplified to " + (inc ? "++x" : "--x"));
statements.remove(statements.size() - 1);
// Replace the thing on the stack with the `--x`
stack.pop();
stack.push(inc ? new PreIncrementOperation(incOperation) : new PreDecrementOperation(incOperation));
// Store the `x` in the register.
int registerNumber = ((StoreRegisterOperation) statement).getRegisterNumber();
context.getRegisters().set(registerNumber, incOperation);
return true;
}
}
}
}
return false;
}
protected boolean handleXPlusPlus(Operation statement, Stack<Operation> stack) {
// get top of stack
Operation stackTop = stack.peek();
// get all operations except stack top
List<Operation> allStackOperations = new ArrayList<Operation>();
for (int i = 0; i<stack.size()-1; i++) {
allStackOperations.addAll(getAllUnderlyingOperations(stack.get(i)));
}
boolean inc = statement instanceof AssignIncrementOperation;
boolean dec = statement instanceof AssignDecrementOperation;
if (inc || dec) {
Operation incOperation = ((UnaryOperation)statement).getOp();
if (!allStackOperations.contains(incOperation)) {
if (stackTop.equals(incOperation)) {
logger.debug("Simplified to "+ (inc ? "x++" : "x--"));
// remove last statement and change top of stack
statements.remove(statements.size()-1);
stack.pop();
stack.push(inc ? new PostIncrementOperation(incOperation) : new PostDecrementOperation(incOperation));
return true;
}
// check registers
// List<Operation> registerOperations = context.getRegisters();
// if (!registerOperations.isEmpty() && registerOperations.get(0) instanceof RegisterHandle) {
// RegisterHandle registerHandle = (RegisterHandle) registerOperations.get(0);
// if (registerHandle.getUndelrlyingOp() instanceof SimpleIncrementOperation) {
// SimpleIncrementOperation simpleIncrementOperation = (SimpleIncrementOperation) registerHandle.getUndelrlyingOp();
// if (incOperation.equals(simpleIncrementOperation.getOp())) {
// logger.debug("Simplified to ++x");
// // remove last statement and change register(0)
// statements.remove(statements.size()-1);
// statements.remove(registerHandle.getStoreRegisterOp());
// context.getRegisters().set(0, new PreIncrementOperation(incOperation));
// return true;
// }
// }
// }
}
int getVariableCount = 0;
int getVariableIdx = 0;
Operation getVariableOp = null;
for (int stackIdx = 0; stackIdx < stack.size() ; stackIdx++) {
Operation stackOperation = stack.get(stackIdx);
if (stackOperation instanceof GetVariableOperation) {
GetVariableOperation variableOp = (GetVariableOperation) stackOperation;
if (incOperation.equals(variableOp)) {
getVariableCount++;
getVariableIdx = stackIdx;
getVariableOp = variableOp;
}
}
}
if (getVariableCount == 1) {
logger.debug("One getVariable found. Replacing it with x++ and removing last statement.");
statements.remove(statements.size()-1);
stack.set(getVariableIdx, inc ? new PostIncrementOperation(getVariableOp) : new PostDecrementOperation(getVariableOp));
return true;
}
}
return false;
}
protected List<Operation> getAllUnderlyingOperations(Operation operation) {
List<Operation> underlyingOperations = new ArrayList<Operation>();
underlyingOperations.add(operation);
for (Operation op : operation.getOperations()) {
underlyingOperations.addAll(getAllUnderlyingOperations(op));
}
return underlyingOperations;
}
protected void handlePush(Push action, Stack<Operation> stack) {
List<StackValue> stackValues = action.getValues();
for (StackValue stackValue : stackValues) {
switch (stackValue.getType()) {
case StackValue.TYPE_STRING :
case StackValue.TYPE_FLOAT :
case StackValue.TYPE_NULL :
case StackValue.TYPE_UNDEFINED :
case StackValue.TYPE_BOOLEAN :
case StackValue.TYPE_DOUBLE :
case StackValue.TYPE_INTEGER :
logger.debug("STACK, pushing: " + stackValue);
stack.push(stackValue);
break;
case StackValue.TYPE_CONSTANT_8 :
int index8 = stackValue.getConstant8();
Operation constant8 = (context.getConstants().size() > index8) ? new StackValue(context.getConstants().get(index8)) : new UndefinedStackValue();
logger.debug("STACK, pushing: " + stackValue+" => " + constant8);
stack.push(constant8);
break;
case StackValue.TYPE_CONSTANT_16 :
int index16 = stackValue.getConstant16();
Operation constant16 = (context.getConstants().size() > index16) ? new StackValue(context.getConstants().get(index16)) : new UndefinedStackValue();
logger.debug("STACK, pushing: " + stackValue+" => " + constant16);
stack.push(constant16);
break;
case StackValue.TYPE_REGISTER :
// logger.debug("V:"+stackValue);
Operation registerValue;
if (context.getRegisters().size()>stackValue.getRegisterNumber()) {
registerValue = context.getRegisters().get(stackValue.getRegisterNumber());
if (registerValue == null) {
registerValue = new NullStackValue();
}
} else {
logger.error("Reference to register #"+stackValue.getRegisterNumber()+", but registers size is "+context.getRegisters().size()+". Pushing undefined. (Fixed by breaking out here, to be verified!!!) This error causes a subsequent error POPing");
registerValue = new UndefinedStackValue();
// break;
}
logger.debug("STACK, pushing: " + stackValue+" => "+registerValue);
stack.push(registerValue);
// Let's only do this for "simple" stack values
// Without it we would get all DefinedFunction2 parameters wrapped in an `eval`.
if (registerValue instanceof StackValue) {
GetVariableOperation item = new GetVariableOperation(stack);
logger.debug("STACK, pushing: " + item);
stack.push(item); // This treats everything that is in a register as a variable, works in all tests I wrote, but that's all the proof I have (wk).
}
break;
default:
logger.error("Unknown stack value type = "+stackValue.getType());
}
}
}
private void postProcessStatements() {
postProcessIncrements();
postProcessFor();
}
private void postProcessFor() {
// TODO Check "for" statements
for (int j=0; j<statements.size(); j++) {
Operation statement = statements.get(j);
if (statement.getClass().equals(WhileOperation.class) && j>0) {
WhileOperation whileOperation = (WhileOperation)statement;
// get condition
Operation condition = whileOperation.getCondition();
// get previous statement
Operation prevStatement = statements.get(j-1);
// get last while operation
if (!whileOperation.getInlineOperations().isEmpty()) {
Operation lastWhileOp = whileOperation.getInlineOperations().get(whileOperation.getInlineOperations().size()-1);
}
}
}
}
private void postProcessIncrements() {
for (int j=0; j<statements.size(); j++) {
Operation statement = statements.get(j);
}
}
private static Operation getRealStatement(Operation statement) {
return (statement instanceof OperationFactory) ? ((OperationFactory)statement).getObject() : statement;
}
public List<Operation> getOperations() {
return statements;
}
public void setExecutionContext(ExecutionContext context) {
this.context = context;
}
}