| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.flex.abc.semantics; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import static org.apache.flex.abc.ABCConstants.*; |
| import org.apache.flex.abc.graph.IBasicBlock; |
| import org.apache.flex.abc.graph.IFlowgraph; |
| import org.apache.flex.abc.visitors.IDiagnosticsVisitor; |
| import org.apache.flex.abc.visitors.IFlowGraphVisitor; |
| |
| /** |
| * A FrameCountVisitor tracks the stack, scope, local, and slot numbers |
| * encountered in a method body, so that the MethodBodyInfo can set its |
| * max_stack, max_scope, max_local, and max_slot values. |
| */ |
| public class FrameCountVisitor implements IFlowGraphVisitor |
| { |
| /** |
| * Construct a new FrameCountVisitor. |
| * |
| * @param mbi - the MethodBodyInfo to analyze. |
| * @param diagnosticsVisitor - a sink for diagnostics. |
| * @param initial_scope - caller's a priori initial scope depth. |
| */ |
| FrameCountVisitor(MethodBodyInfo mbi, IDiagnosticsVisitor diagnosticsVisitor, int initial_scope) |
| { |
| this.diagnosticsVisitor = diagnosticsVisitor; |
| this.cfg = mbi.getCfg(); |
| this.mbi = mbi; |
| this.initial_scope = initial_scope; |
| this.exceptions = mbi.getExceptions(); |
| this.instructionIndex = 0; |
| } |
| |
| /** |
| * The MethodBodyInfo that generated the IFlowgraph. |
| */ |
| final MethodBodyInfo mbi; |
| |
| /** |
| * Receiver of any diagnostic output. |
| */ |
| final IDiagnosticsVisitor diagnosticsVisitor; |
| |
| /** |
| * The control flow graph, denormalized from the MethodBodyInfo. |
| */ |
| final IFlowgraph cfg; |
| |
| /** |
| * Caller's a priori initial scope depth. |
| */ |
| final int initial_scope; |
| |
| /** |
| * Exception information from the method body. |
| */ |
| final Iterable<ExceptionInfo> exceptions; |
| |
| int max_stack; |
| int max_local; |
| int max_scope; |
| int max_slot; |
| |
| /** |
| * Set if we encounter a newclass instruction. |
| */ |
| boolean hasNewclass = false; |
| |
| /** |
| * Stack depths on entry to blocks |
| */ |
| Map<IBasicBlock, Integer> stkin = new HashMap<IBasicBlock, Integer>(); |
| |
| /** |
| * Scope depths on entry to blocks |
| */ |
| Map<IBasicBlock, Integer> scpin = new HashMap<IBasicBlock, Integer>(); |
| |
| int stkdepth = 0; |
| int scpdepth = 0; |
| |
| int instructionIndex; |
| |
| IBasicBlock currentBlock; |
| |
| /** |
| * Visit a new Block. |
| * |
| * @param b - the Block to visit. |
| * @return true if the walker should continue visiting the block. |
| */ |
| public boolean visitBlock(IBasicBlock b) |
| { |
| this.currentBlock = b; |
| |
| if (this.cfg.isCatchTarget(b)) |
| { |
| stkdepth = 1; |
| scpdepth = 0; |
| } |
| else if (stkin.containsKey(b)) |
| { |
| stkdepth = stkin.get(b); |
| scpdepth = scpin.get(b); |
| } |
| // else use the current values. |
| return true; |
| } |
| |
| /** |
| * Visit an Instruction within the most recently-visited Block. |
| * |
| * @param i - the Instruction. |
| */ |
| public void visitInstruction(Instruction i) |
| { |
| switch (i.opcode) |
| { |
| case OP_add: |
| case OP_add_i: |
| case OP_astypelate: |
| case OP_bitand: |
| case OP_bitor: |
| case OP_bitxor: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| |
| case OP_applytype: |
| case OP_astype: |
| case OP_bitnot: |
| case OP_bkpt: |
| case OP_bkptline: |
| case OP_checkfilter: |
| case OP_coerce: |
| case OP_coerce_a: |
| case OP_coerce_i: |
| case OP_coerce_d: |
| case OP_coerce_s: |
| case OP_coerce_u: |
| case OP_convert_b: |
| case OP_convert_d: |
| case OP_convert_i: |
| case OP_convert_o: |
| case OP_convert_s: |
| case OP_convert_u: |
| case OP_debug: |
| case OP_debugfile: |
| case OP_debugline: |
| case OP_declocal: |
| case OP_declocal_i: |
| case OP_decrement: |
| case OP_decrement_i: |
| case OP_dxns: |
| case OP_esc_xattr: |
| case OP_esc_xelem: |
| case OP_inclocal: |
| case OP_inclocal_i: |
| case OP_increment: |
| case OP_increment_i: |
| case OP_jump: |
| case OP_kill: |
| case OP_label: |
| case OP_negate: |
| case OP_negate_i: |
| case OP_returnvoid: |
| case OP_nop: |
| case OP_not: |
| case OP_swap: |
| case OP_timestamp: |
| case OP_typeof: |
| case OP_unplus: |
| // Net effect zero. |
| break; |
| |
| case OP_call: |
| stkdepth = adjustValueStack(stkdepth, -(i.getImmediate() + 1)); |
| break; |
| |
| case OP_callmethod: |
| assert false : "internal only instruction!"; |
| stkdepth = adjustValueStack(stkdepth, -i.getImmediate()); |
| break; |
| |
| case OP_callstatic: |
| stkdepth = adjustValueStack(stkdepth, -((Integer)i.getOperand(1))); |
| break; |
| |
| case OP_callproperty: |
| case OP_callproplex: |
| case OP_callsuper: |
| case OP_constructprop: |
| stkdepth = adjustValueStack(stkdepth, -((Integer)i.getOperand(1)) + runtimeNameAllowance((Name)i.getOperand(0))); |
| break; |
| |
| case OP_callpropvoid: |
| case OP_callsupervoid: |
| // void calls do not push a result |
| stkdepth = adjustValueStack(stkdepth, -((Integer)i.getOperand(1)) + runtimeNameAllowance((Name)i.getOperand(0)) - 1); |
| break; |
| |
| case OP_construct: |
| stkdepth = adjustValueStack(stkdepth, -i.getImmediate()); |
| break; |
| |
| case OP_constructsuper: |
| stkdepth = adjustValueStack(stkdepth, -i.getImmediate() - 1); |
| break; |
| |
| case OP_deleteproperty: |
| stkdepth = adjustValueStack(stkdepth, runtimeNameAllowance((Name)i.getOperand(0))); |
| break; |
| |
| case OP_divide: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| case OP_dup: |
| stkdepth = adjustValueStack(stkdepth, 1); |
| break; |
| case OP_dxnslate: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| case OP_equals: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| case OP_finddef: |
| case OP_findproperty: |
| case OP_findpropstrict: |
| stkdepth = adjustValueStack(stkdepth, 1 + runtimeNameAllowance((Name)i.getOperand(0))); |
| break; |
| |
| case OP_getdescendants: |
| case OP_getproperty: |
| case OP_getsuper: |
| stkdepth = adjustValueStack(stkdepth, runtimeNameAllowance((Name)i.getOperand(0))); |
| break; |
| |
| case OP_getglobalscope: |
| case OP_getglobalslot: |
| case OP_getlex: |
| case OP_getouterscope: |
| stkdepth = adjustValueStack(stkdepth, 1); |
| break; |
| |
| case OP_getlocal: |
| stkdepth = adjustValueStack(stkdepth, 1); |
| adjustMaxLocal(i.getImmediate()); |
| if (i.getImmediate() < 4) |
| i.opcode = OP_getlocal0 + i.getImmediate(); |
| break; |
| case OP_getlocal0: |
| case OP_getlocal1: |
| case OP_getlocal2: |
| case OP_getlocal3: |
| stkdepth = adjustValueStack(stkdepth, 1); |
| adjustMaxLocal(i.opcode - OP_getlocal0); |
| break; |
| |
| case OP_getslot: |
| if (i.getImmediate() > max_slot) |
| max_slot = i.getImmediate(); |
| break; |
| |
| case OP_getscopeobject: |
| stkdepth = adjustValueStack(stkdepth, 1); |
| break; |
| |
| case OP_greaterequals: |
| case OP_greaterthan: |
| case OP_hasnext: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| case OP_hasnext2: |
| { |
| // Both hasnext2 operands are locals. |
| adjustMaxLocal((Integer)i.getOperand(0)); |
| adjustMaxLocal((Integer)i.getOperand(1)); |
| stkdepth = adjustValueStack(stkdepth, 1); |
| break; |
| } |
| case OP_ifeq: |
| case OP_ifge: |
| case OP_ifgt: |
| case OP_ifle: |
| case OP_iflt: |
| case OP_ifnge: |
| case OP_ifngt: |
| case OP_ifnle: |
| case OP_ifnlt: |
| case OP_ifne: |
| case OP_ifstricteq: |
| case OP_ifstrictne: |
| stkdepth = adjustValueStack(stkdepth, -2); |
| break; |
| case OP_iffalse: |
| case OP_iftrue: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| |
| case OP_in: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| |
| case OP_initproperty: |
| stkdepth = adjustValueStack(stkdepth, -2 + runtimeNameAllowance((Name)i.getOperand(0))); |
| break; |
| |
| case OP_instanceof: |
| case OP_istypelate: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| |
| case OP_istype: |
| stkdepth = adjustValueStack(stkdepth, runtimeNameAllowance((Name)i.getOperand(0))); |
| break; |
| |
| case OP_lessequals: |
| case OP_lessthan: |
| case OP_lookupswitch: |
| case OP_lshift: |
| case OP_modulo: |
| case OP_multiply: |
| case OP_multiply_i: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| |
| case OP_newactivation: |
| case OP_newcatch: |
| case OP_newfunction: |
| stkdepth = adjustValueStack(stkdepth, 1); |
| break; |
| |
| case OP_newarray: |
| stkdepth = adjustValueStack(stkdepth, 1 - i.getImmediate()); |
| break; |
| |
| case OP_newobject: |
| // The operands are name-value pairs. |
| stkdepth = adjustValueStack(stkdepth, 1 - (i.getImmediate() * 2)); |
| break; |
| |
| case OP_nextname: |
| case OP_nextvalue: |
| case OP_pop: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| |
| case OP_popscope: |
| scpdepth = adjustScopeStack(scpdepth, -1); |
| break; |
| |
| case OP_pushbyte: |
| case OP_pushdouble: |
| case OP_pushfalse: |
| case OP_pushint: |
| case OP_pushnamespace: |
| case OP_pushnan: |
| case OP_pushnull: |
| case OP_pushshort: |
| case OP_pushstring: |
| case OP_pushtrue: |
| case OP_pushuint: |
| case OP_pushundefined: |
| stkdepth = adjustValueStack(stkdepth, 1); |
| break; |
| |
| case OP_pushscope: |
| case OP_pushwith: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| scpdepth = adjustScopeStack(scpdepth, 1); |
| break; |
| |
| case OP_returnvalue: |
| case OP_rshift: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| |
| case OP_setlocal: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| adjustMaxLocal(i.getImmediate()); |
| if (i.getImmediate() < 4) |
| i.opcode = OP_setlocal0 + i.getImmediate(); |
| break; |
| case OP_setlocal0: |
| case OP_setlocal1: |
| case OP_setlocal2: |
| case OP_setlocal3: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| adjustMaxLocal(i.opcode - OP_setlocal0); |
| break; |
| |
| case OP_setglobalslot: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| |
| case OP_setproperty: |
| case OP_setsuper: |
| stkdepth = adjustValueStack(stkdepth, -2 + runtimeNameAllowance((Name)i.getOperand(0))); |
| break; |
| |
| case OP_setslot: |
| stkdepth = adjustValueStack(stkdepth, -2); |
| if (max_slot < i.getImmediate()) |
| max_slot = i.getImmediate(); |
| break; |
| |
| case OP_strictequals: |
| case OP_subtract: |
| case OP_subtract_i: |
| case OP_throw: |
| case OP_urshift: |
| stkdepth = adjustValueStack(stkdepth, -1); |
| break; |
| |
| case OP_newclass: |
| this.hasNewclass = true; |
| break; |
| |
| case OP_li8: |
| case OP_li16: |
| case OP_li32: |
| case OP_lf32: |
| case OP_lf64: |
| case OP_sxi1: |
| case OP_sxi8: |
| case OP_sxi16: |
| // consume one stack entry, and produce 1 stack entry. |
| break; |
| case OP_si8: |
| case OP_si16: |
| case OP_si32: |
| case OP_sf32: |
| case OP_sf64: |
| // consume two stack entries |
| stkdepth = adjustValueStack(stkdepth, -2); |
| break; |
| |
| case OP_callinterface: |
| case OP_callsuperid: |
| case OP_deletepropertylate: |
| case OP_setpropertylate: |
| assert false : "internal only instruction!"; |
| break; |
| default: |
| assert false : "unknown instruction!"; |
| break; |
| } |
| this.instructionIndex++; |
| } |
| |
| public void visitEnd(IBasicBlock b) |
| { |
| for (IBasicBlock s : b.getSuccessors()) |
| { |
| if (!stkin.containsKey(s)) |
| { |
| stkin.put(s, stkdepth); |
| scpin.put(s, scpdepth); |
| } |
| } |
| } |
| |
| private int adjustValueStack(int stkdepth, int incr) |
| { |
| stkdepth += incr; |
| |
| if (stkdepth < 0) |
| this.diagnosticsVisitor.operandStackUnderflow(this.mbi, this.cfg, this.currentBlock, instructionIndex); |
| |
| if (stkdepth > this.max_stack) |
| this.max_stack = stkdepth; |
| return stkdepth; |
| } |
| |
| private int adjustScopeStack(int scpdepth, int incr) |
| { |
| scpdepth += incr; |
| |
| if (scpdepth < 0) |
| this.diagnosticsVisitor.scopeStackUnderflow(this.mbi, this.cfg, this.currentBlock, instructionIndex); |
| |
| if (scpdepth > (this.max_scope - this.initial_scope)) |
| { |
| this.max_scope = scpdepth + this.initial_scope; |
| } |
| return scpdepth; |
| } |
| |
| private void adjustMaxLocal(int idx) |
| { |
| if (max_local <= idx) |
| max_local = idx + 1; |
| } |
| |
| /** |
| * Examine a Name and compute the number of value stack elements it will |
| * need in its evaluation. |
| * |
| * @param operand - the runtime name. May be null if the operation can |
| * function without a name operand. |
| * @return the number of value stack elements evaluating this Name requires. |
| */ |
| private int runtimeNameAllowance(Object operand) |
| { |
| return operand instanceof Name ? |
| -((Name)operand).runtimeNameAllowance() : |
| 0; |
| } |
| |
| public boolean hasNewclassInstruction() |
| { |
| return this.hasNewclass; |
| } |
| } |