// ---------------------------------------------------------------------- // Back propogation // ---------------------------------------------------------------------- // Refine given before machine state account for backwards flow from after machine state. // Only propogates liveness, thus we only need to look for read/writes of arguments and locals, // either directly (via ldarg/starg/ldloc/stloc) or indirectly (via ldind/ldobj/cpobj). public void BackwardInstruction(InstructionContext context, int index, MachineState beforeState, MachineState afterState, BoolRef changed) { var instruction = context.Block.Body[index]; switch (instruction.Flavor) { case InstructionFlavor.Misc: { var misci = (MiscInstruction)instruction; switch (misci.Op) { case MiscOp.LdindRef: beforeState.ReadPointer(afterState, beforeState.PeekPointsTo(0), changed); return; case MiscOp.StindRef: // May have overwritten an arg or local, but don't know exactly which one, so must // be conservative and leave everything alive break; case MiscOp.Nop: case MiscOp.Break: case MiscOp.Dup: case MiscOp.Pop: case MiscOp.Ldnull: case MiscOp.Ckfinite: case MiscOp.Throw: case MiscOp.Rethrow: case MiscOp.LdelemRef: case MiscOp.StelemRef: case MiscOp.Ldlen: case MiscOp.Ret: case MiscOp.RetVal: case MiscOp.Endfilter: case MiscOp.Endfinally: break; default: throw new ArgumentOutOfRangeException(); } break; } case InstructionFlavor.ArgLocal: { var sli = (ArgLocalInstruction)instruction; switch (sli.Op) { case ArgLocalOp.Ld: beforeState.ReadArgLocal(afterState, sli.ArgLocal, sli.Index, changed); return; case ArgLocalOp.Lda: // Assume pointer we are creating is read from, and to be conservative don't // assume it is written to. beforeState.ReadArgLocal(afterState, sli.ArgLocal, sli.Index, changed); break; case ArgLocalOp.St: beforeState.WriteArgLocal(afterState, sli.ArgLocal, sli.Index, changed); return; default: throw new ArgumentOutOfRangeException(); } break; } case InstructionFlavor.Type: { var typei = (TypeInstruction)instruction; switch (typei.Op) { case TypeOp.Ldobj: beforeState.ReadPointer(afterState, beforeState.PeekPointsTo(0), changed); return; case TypeOp.Stobj: // As above, can't be sure which args or locals will be written to break; case TypeOp.Cpobj: // As above, can't be sure which args or locals will be written to // But can handle read safely beforeState.ReadPointer(afterState, beforeState.PeekPointsTo(0), changed); return; case TypeOp.Newarr: case TypeOp.Initobj: case TypeOp.Castclass: case TypeOp.Isinst: case TypeOp.Box: case TypeOp.Unbox: case TypeOp.UnboxAny: case TypeOp.Ldtoken: case TypeOp.Ldelem: case TypeOp.Stelem: break; default: throw new ArgumentOutOfRangeException(); } break; } case InstructionFlavor.Try: { var tryi = (TryInstruction)instruction; var tryContext = new TryBodyInstructionContext(context, index, tryi.Body); BackwardBlock(tryContext, changed); for (var j = 0; j < tryi.Handlers.Count; j++) { var h = tryi.Handlers[j]; var handlerContext = new TryHandlerInstructionContext(context, index, h.Body, j); BackwardBlock(handlerContext, changed); } return; } case InstructionFlavor.Method: { var methi = (MethodInstruction)instruction; switch (methi.Op) { case MethodOp.Call: case MethodOp.Newobj: { // Assume any pointers passed to call are read from var sig = (CST.MethodSignature)methi.Method.ExternalSignature; var skippedArgs = (methi.Op == MethodOp.Newobj ? 1 : 0); var passedArgs = sig.Parameters.Count - skippedArgs; for (var i = passedArgs - 1; i >= 0; i--) { if (sig.Parameters[skippedArgs + i].Style(methEnv) is CST.ManagedPointerTypeStyle) beforeState.ReadPointer (afterState, beforeState.PeekPointsTo(passedArgs - 1 - i), changed); } // Also assume call does not write to any pointers, thus everything remains // alive across call. Ie just fallthough. break; } case MethodOp.Ldftn: case MethodOp.Ldtoken: break; default: throw new ArgumentOutOfRangeException(); } break; } case InstructionFlavor.Unsupported: case InstructionFlavor.Field: case InstructionFlavor.Branch: case InstructionFlavor.Switch: case InstructionFlavor.Compare: case InstructionFlavor.LdElemAddr: case InstructionFlavor.LdInt32: case InstructionFlavor.LdInt64: case InstructionFlavor.LdSingle: case InstructionFlavor.LdDouble: case InstructionFlavor.LdString: case InstructionFlavor.Arith: case InstructionFlavor.Conv: case InstructionFlavor.IfThenElsePseudo: case InstructionFlavor.ShortCircuitingPseudo: case InstructionFlavor.StructuralSwitchPseudo: case InstructionFlavor.LoopPseudo: case InstructionFlavor.WhileDoPseudo: case InstructionFlavor.DoWhilePseudo: case InstructionFlavor.LoopControlPseudo: break; default: throw new ArgumentOutOfRangeException(); } // By default, anything alive in after state must be alive in before state. beforeState.PropogateBackwards(afterState, changed); }
// What are all the source -> target transitions possible due to exceptions from instruction in // context's instruction block? private void AddEffectiveInstructionTransitions(Set<SourceTarget> transitions, InstructionContext context, int index) { var instruction = context.Block.Body[index]; if (instruction.Flavor == InstructionFlavor.Try) { var tryi = (TryInstruction)instruction; var tryContext = new TryBodyInstructionContext(context, index, tryi.Body); var exits = new Seq<Instruction>(); AddExceptionalExits(exits, tryi.Body); AddEffectiveBlockTransitions(transitions, tryContext); for (var i = 0; i < tryi.Handlers.Count; i++) { var handler = tryi.Handlers[i]; var handlerContext = new TryHandlerInstructionContext(context, index, handler.Body, i); AddEffectiveBlockTransitions(transitions, handlerContext); // Could transition from any exceptional exit point of try body to start of this handler foreach (var exit in exits) transitions.Add(new SourceTarget(exit, handler.Body.Body[0].Offset, "throw to handler")); } } else if (instruction.Flavor == InstructionFlavor.Branch) { var bri = (BranchInstruction)instruction; if (bri.Op == BranchOp.Leave) { // Leave will enter each finally handler between here and the target instruction. // Initially, control comes from just the leave instruction itself. var sources = new Seq<Instruction> { bri }; var currContext = context; while (true) { if (currContext.Block.ContainsOffset(bri.Target)) { // Found target of leave foreach (var source in sources) transitions.Add(new SourceTarget(source, bri.Target, "leave to target")); break; } currContext = currContext.ParentContext; if (currContext == null) throw new InvalidOperationException("no target for leave"); var outerTryi = currContext.ParentInstruction as TryInstruction; if (outerTryi != null) { var i = outerTryi.FinallyIndex; if (i >= 0) { // Found next outer finally. Leave will go via start of this block, and exit at // each endfinally. var handler = outerTryi.Handlers[i]; foreach (var source in sources) transitions.Add(new SourceTarget(source, handler.Body.Body[0].Offset, "leave to finally")); sources = new Seq<Instruction>(); foreach (var handlerInstruction in handler.Body.Body) { if (handlerInstruction.Code == InstructionCode.Endfinally) sources.Add(handlerInstruction); } } } } } // else: normal control-flow transitions cary over the entire machine state, so we deal // with them during forward analysis below } else if (instruction.Flavor == InstructionFlavor.Misc) { var misci = (MiscInstruction)instruction; if (misci.Op == MiscOp.Endfinally) { // If entered a finally block because of a leave from a try or catch block, then // could continue to that offset, however we account for that above when handling the leave. // If entered a finally or fault block because of an exception, then could continue to start // of each enclosing catch, fault or finally block. Since a catch clause may not match, must // keep including outer blocks. However, a fault or finally block will always fire, so can stop. // ParentInstruction of context will be the try with fault/finally handler we are currently in, // so skip it. var currContext = context; var catchesOnly = true; while (catchesOnly && currContext.ParentContext != null) { currContext = currContext.ParentContext; var outerTryi = currContext.ParentInstruction as TryInstruction; if (outerTryi != null) { foreach (var outerHandler in outerTryi.Handlers) { transitions.Add(new SourceTarget(instruction, outerHandler.Body.Body[0].Offset, "continue up chain")); if (outerHandler.Flavor == HandlerFlavor.Fault || outerHandler.Flavor == HandlerFlavor.Finally) catchesOnly = false; } } } // else: will leave method } // else: no additional control flow } // else: no aditional control flow }