internal void Define(LabelScopeInfo block) { for (LabelScopeInfo info = block; info != null; info = info.Parent) { if (info.ContainsTarget(this._node)) { throw System.Linq.Expressions.Error.LabelTargetAlreadyDefined(this._node.Name); } } this._definitions.Add(block); block.AddLabelInfo(this._node, this); if (this._definitions.Count == 1) { foreach (LabelScopeInfo info2 in this._references) { this.ValidateJump(info2); } } else { if (this._acrossBlockJump) { throw System.Linq.Expressions.Error.AmbiguousJump(this._node.Name); } this._labelDefined = false; } }
internal void Reference(LabelScopeInfo block) { _references.Add(block); if (_definitions.Count > 0) { ValidateJump(block); } }
private void ValidateJump(LabelScopeInfo reference) { this._opCode = this._canReturn ? OpCodes.Ret : OpCodes.Br; for (LabelScopeInfo info = reference; info != null; info = info.Parent) { if (this._definitions.Contains(info)) { return; } if ((info.Kind == LabelScopeKind.Finally) || (info.Kind == LabelScopeKind.Filter)) { break; } if ((info.Kind == LabelScopeKind.Try) || (info.Kind == LabelScopeKind.Catch)) { this._opCode = OpCodes.Leave; } } this._acrossBlockJump = true; if ((this._node != null) && (this._node.Type != typeof(void))) { throw System.Linq.Expressions.Error.NonLocalJumpWithValue(this._node.Name); } if (this._definitions.Count > 1) { throw System.Linq.Expressions.Error.AmbiguousJump(this._node.Name); } LabelScopeInfo first = this._definitions.First <LabelScopeInfo>(); LabelScopeInfo info3 = Helpers.CommonNode <LabelScopeInfo>(first, reference, b => b.Parent); this._opCode = this._canReturn ? OpCodes.Ret : OpCodes.Br; for (LabelScopeInfo info4 = reference; info4 != info3; info4 = info4.Parent) { if (info4.Kind == LabelScopeKind.Finally) { throw System.Linq.Expressions.Error.ControlCannotLeaveFinally(); } if (info4.Kind == LabelScopeKind.Filter) { throw System.Linq.Expressions.Error.ControlCannotLeaveFilterTest(); } if ((info4.Kind == LabelScopeKind.Try) || (info4.Kind == LabelScopeKind.Catch)) { this._opCode = OpCodes.Leave; } } for (LabelScopeInfo info5 = first; info5 != info3; info5 = info5.Parent) { if (!info5.CanJumpInto) { if (info5.Kind == LabelScopeKind.Expression) { throw System.Linq.Expressions.Error.ControlCannotEnterExpression(); } throw System.Linq.Expressions.Error.ControlCannotEnterTry(); } } }
private void CheckTry() { // Try inside a filter is not verifiable for (LabelScopeInfo j = _labelBlock; j != null; j = j.Parent) { if (j.Kind == LabelScopeKind.Filter) { throw Error.TryNotAllowedInFilter(); } } }
private static void CheckTry(LabelScopeInfo labelBlock) { // Try inside a filter is not verifiable foreach (var j in SequenceHelper.ExploreSequenceUntilNull(labelBlock, found => found.Parent)) { if (j.Kind == LabelScopeKind.Filter) { throw new InvalidOperationException("Try expression is not allowed inside a filter body."); } } }
private void EmitExpressionAsVoid(Expression node, CompilationFlags flags = CompilationFlags.EmitAsNoTail) { var labelScopeChangeInfo = GetLabelScopeChangeInfo(true, _labelBlock, node); if (labelScopeChangeInfo.HasValue) { _labelBlock = new LabelScopeInfo(labelScopeChangeInfo.Value.parent, labelScopeChangeInfo.Value.kind); DefineBlockLabels(labelScopeChangeInfo.Value.nodes); } switch (node.NodeType) { case ExpressionType.Assign: EmitAssign((AssignBinaryExpression)node, CompilationFlags.EmitAsVoidType); break; case ExpressionType.Block: Emit((BlockExpression)node, UpdateEmitAsTypeFlag(flags, CompilationFlags.EmitAsVoidType)); break; case ExpressionType.Throw: EmitThrow((UnaryExpression)node, CompilationFlags.EmitAsVoidType); break; case ExpressionType.Goto: EmitGotoExpression(node, UpdateEmitAsTypeFlag(flags, CompilationFlags.EmitAsVoidType)); break; case ExpressionType.Constant: case ExpressionType.Default: case ExpressionType.Parameter: // no-op break; default: if (node.Type == typeof(void)) { EmitExpression(node, UpdateEmitExpressionStartFlag(flags, CompilationFlags.EmitNoExpressionStart)); } else { EmitExpression(node, CompilationFlags.EmitAsNoTail | CompilationFlags.EmitNoExpressionStart); IL.Emit(OpCodes.Pop); } break; } if (labelScopeChangeInfo.HasValue) { _labelBlock = labelScopeChangeInfo.Value.parent; } }
// We don't want "ref" parameters to modify values of expressions // except where it would in IL: locals, args, fields, and array elements // (Unbox is an exception, it's intended to emit a ref to the original // boxed value) private void EmitAddress(Expression node, Type type, CompilationFlags flags = CompilationFlags.EmitExpressionStart) { var emitStart = (flags & CompilationFlags.EmitExpressionStartMask) == CompilationFlags.EmitExpressionStart; var labelScopeChangeInfo = GetLabelScopeChangeInfo(emitStart, _labelBlock, node); if (labelScopeChangeInfo.HasValue) { _labelBlock = new LabelScopeInfo(labelScopeChangeInfo.Value.parent, labelScopeChangeInfo.Value.kind); DefineBlockLabels(labelScopeChangeInfo.Value.nodes); } switch (node.NodeType) { default: EmitExpressionAddress(node, type); break; case ExpressionType.ArrayIndex: AddressOf((BinaryExpression)node, type); break; case ExpressionType.Parameter: AddressOf((ParameterExpression)node, type); break; case ExpressionType.MemberAccess: AddressOf((MemberExpression)node, type); break; case ExpressionType.Unbox: AddressOf((UnaryExpression)node, type); break; case ExpressionType.Call: AddressOf((MethodCallExpression)node, type); break; case ExpressionType.Index: AddressOf((IndexExpression)node, type); break; } if (labelScopeChangeInfo.HasValue) { _labelBlock = labelScopeChangeInfo.Value.parent; } }
private void CheckRethrow() { // Rethrow is only valid inside a catch. for (LabelScopeInfo j = _labelBlock; j != null; j = j.Parent) { if (j.Kind == LabelScopeKind.Catch) { return; } else if (j.Kind == LabelScopeKind.Finally) { // Rethrow from inside finally is not verifiable break; } } throw Error.RethrowRequiresCatch(); }
private void EmitLoopExpression(Expression expr) { var node = (LoopExpression)expr; var parent = _labelBlock; _labelBlock = new LabelScopeInfo(parent, LabelScopeKind.Statement); var breakTarget = DefineLabel(node.BreakLabel); var continueTarget = DefineLabel(node.ContinueLabel); continueTarget.MarkWithEmptyStack(); EmitExpressionAsVoid(node.Body); _labelBlock = parent; breakTarget.MarkWithEmptyStack(); }
private static void CheckRethrow(LabelScopeInfo labelBlock) { // Rethrow is only valid inside a catch. foreach (var j in SequenceHelper.ExploreSequenceUntilNull(labelBlock, found => found.Parent)) { if (j.Kind == LabelScopeKind.Catch) { return; } if (j.Kind == LabelScopeKind.Finally) { // Rethrow from inside finally is not verifiable break; } } throw new InvalidOperationException("Rethrow statement is valid only inside a Catch block."); }
private void EmitExpressionAndBranch(bool branchValue, Expression node, Label label) { Debug.Assert(node.Type == typeof(bool)); var labelScopeChangeInfo = GetLabelScopeChangeInfo(true, _labelBlock, node); if (labelScopeChangeInfo.HasValue) { _labelBlock = new LabelScopeInfo(labelScopeChangeInfo.Value.parent, labelScopeChangeInfo.Value.kind); DefineBlockLabels(labelScopeChangeInfo.Value.nodes); } switch (node.NodeType) { case ExpressionType.Not: EmitBranchNot(branchValue, (UnaryExpression)node, label); break; case ExpressionType.AndAlso: case ExpressionType.OrElse: EmitBranchLogical(branchValue, (BinaryExpression)node, label); break; case ExpressionType.Block: EmitBranchBlock(branchValue, (BlockExpression)node, label); break; case ExpressionType.Equal: case ExpressionType.NotEqual: EmitBranchComparison(branchValue, (BinaryExpression)node, label); break; default: EmitExpression(node, CompilationFlags.EmitAsNoTail | CompilationFlags.EmitNoExpressionStart); EmitBranchOp(branchValue, label); break; } if (labelScopeChangeInfo.HasValue) { _labelBlock = labelScopeChangeInfo.Value.parent; } }
// Emits the address of the expression, returning the write back if necessary // // For properties, we want to write back into the property if it's // passed byref. private WriteBack?EmitAddressWriteBack(Expression node, Type type) { var labelScopeChangeInfo = GetLabelScopeChangeInfo(true, _labelBlock, node); if (labelScopeChangeInfo.HasValue) { _labelBlock = new LabelScopeInfo(labelScopeChangeInfo.Value.parent, labelScopeChangeInfo.Value.kind); DefineBlockLabels(labelScopeChangeInfo.Value.nodes); } WriteBack?result = null; if (TypeUtils.AreEquivalent(type, node.Type)) { switch (node.NodeType) { case ExpressionType.MemberAccess: result = AddressOfWriteBack((MemberExpression)node); break; case ExpressionType.Index: result = AddressOfWriteBack((IndexExpression)node); break; default: break; } } if (result == null) { EmitAddress(node, type, CompilationFlags.EmitAsNoTail | CompilationFlags.EmitNoExpressionStart); } if (labelScopeChangeInfo.HasValue) { _labelBlock = labelScopeChangeInfo.Value.parent; } return(result); }
private void EmitCatchStart(CatchBlock cb) { if (cb.Filter == null) { EmitSaveExceptionOrPop(cb); return; } // emit filter block. Filter blocks are untyped so we need to do // the type check ourselves. var endFilter = IL.DefineLabel(); var rightType = IL.DefineLabel(); // skip if it's not our exception type, but save // the exception if it is so it's available to the // filter IL.Emit(OpCodes.Isinst, cb.Test); IL.Emit(OpCodes.Dup); IL.Emit(OpCodes.Brtrue, rightType); IL.Emit(OpCodes.Pop); IL.Emit(OpCodes.Ldc_I4_0); IL.Emit(OpCodes.Br, endFilter); // it's our type, save it and emit the filter. IL.MarkLabel(rightType); EmitSaveExceptionOrPop(cb); var parent = _labelBlock; _labelBlock = new LabelScopeInfo(parent, LabelScopeKind.Filter); EmitExpression(cb.Filter); _labelBlock = parent; // begin the catch, clear the exception, we've // already saved it IL.MarkLabel(endFilter); IL.BeginCatchBlock(null); IL.Emit(OpCodes.Pop); }
// Returns true if the label was successfully defined // or false if the label is now ambiguous internal void Define(LabelScopeInfo block) { // Prevent the label from being shadowed, which enforces cleaner // trees. Also we depend on this for simplicity (keeping only one // active IL Label per LabelInfo) foreach (var j in SequenceHelper.ExploreSequenceUntilNull(block, found => found.Parent)) { if (j.ContainsTarget(_node !)) { throw new InvalidOperationException($"Cannot redefine label '{_node!.Name}' in an inner block."); } } _definitions.Add(block); block.AddLabelInfo(_node !, this); // Once defined, validate all jumps if (_definitions.Count == 1) { foreach (var r in _references) { ValidateJump(r); } } else { // Was just redefined, if we had any across block jumps, they're // now invalid if (_acrossBlockJump) { throw new InvalidOperationException($"Cannot jump to ambiguous label '{_node?.Name}'."); } // For local jumps, we need a new IL label // This is okay because: // 1. no across block jumps have been made or will be made // 2. we don't allow the label to be shadowed _labelDefined = false; } }
// Returns true if the label was successfully defined // or false if the label is now ambiguous internal void Define(LabelScopeInfo block) { // Prevent the label from being shadowed, which enforces cleaner // trees. Also we depend on this for simplicity (keeping only one // active IL Label per LabelInfo) for (LabelScopeInfo j = block; j != null; j = j.Parent) { if (j.ContainsTarget(_node)) { throw Error.LabelTargetAlreadyDefined(_node.Name); } } _definitions.Add(block); block.AddLabelInfo(_node, this); // Once defined, validate all jumps if (_definitions.Count == 1) { foreach (LabelScopeInfo r in _references) { ValidateJump(r); } } else { // Was just redefined, if we had any across block jumps, they're // now invalid if (_acrossBlockJump) { throw Error.AmbiguousJump(_node.Name); } // For local jumps, we need a new IL label // This is okay because: // 1. no across block jumps have been made or will be made // 2. we don't allow the label to be shadowed _labelDefined = false; } }
private static (LabelScopeInfo parent, LabelScopeKind kind, IList <Expression>?nodes)? GetLabelScopeChangeInfo(bool emitStart, LabelScopeInfo labelBlock, Expression node) { if (!emitStart) { return(null); } // Anything that is "statement-like" -- e.g. has no associated // stack state can be jumped into, with the exception of try-blocks // We indicate this by a "Block" // // Otherwise, we push an "Expression" to indicate that it can't be // jumped into switch (node.NodeType) { default: if (labelBlock.Kind == LabelScopeKind.Expression) { return(null); } return(labelBlock, LabelScopeKind.Expression, null); case ExpressionType.Label: // LabelExpression is a bit special, if it's directly in a // block it becomes associate with the block's scope. Same // thing if it's in a switch case body. if (labelBlock.Kind != LabelScopeKind.Block) { return(labelBlock, LabelScopeKind.Statement, null); } var label = ((LabelExpression)node).Target; if (labelBlock.ContainsTarget(label)) { return(null); } if (labelBlock.Parent?.Kind == LabelScopeKind.Switch && labelBlock.Parent.ContainsTarget(label)) { return(null); } return(labelBlock, LabelScopeKind.Statement, null); case ExpressionType.Block: if (node is SpilledExpressionBlock) { // treat it as an expression goto default; } return(labelBlock.Parent?.Kind != LabelScopeKind.Switch ? (labelBlock, LabelScopeKind.Block, new[] { node }) : (labelBlock, LabelScopeKind.Block, null)); case ExpressionType.Switch: var nodes = new List <Expression>(); var @switch = (SwitchExpression)node; foreach (var c in @switch.Cases) { nodes.Add(c.Body); } if (@switch.DefaultBody != null) { nodes.Add(@switch.DefaultBody); } return(labelBlock, LabelScopeKind.Switch, nodes); // Remove this when Convert(Void) goes away. case ExpressionType.Convert: if (node.Type != typeof(void)) { // treat it as an expression goto default; } return(labelBlock, LabelScopeKind.Statement, null); case ExpressionType.Conditional: case ExpressionType.Loop: case ExpressionType.Goto: return(labelBlock, LabelScopeKind.Statement, null); } }
private void PushLabelBlock(LabelScopeKind type) { _labelBlock = new LabelScopeInfo(_labelBlock, type); }
// Returns true if the label was successfully defined // or false if the label is now ambiguous internal void Define(LabelScopeInfo block) { // Prevent the label from being shadowed, which enforces cleaner // trees. Also we depend on this for simplicity (keeping only one // active IL Label per LabelInfo) for (LabelScopeInfo j = block; j != null; j = j.Parent) { if (j.ContainsTarget(_node)) { throw Error.LabelTargetAlreadyDefined(_node.Name); } } _definitions.Add(block); block.AddLabelInfo(_node, this); // Once defined, validate all jumps if (_definitions.Count == 1) { foreach (var r in _references) { ValidateJump(r); } } else { // Was just redefined, if we had any across block jumps, they're // now invalid if (_acrossBlockJump) { throw Error.AmbiguousJump(_node.Name); } // For local jumps, we need a new IL label // This is okay because: // 1. no across block jumps have been made or will be made // 2. we don't allow the label to be shadowed _labelDefined = false; } }
internal LabelScopeInfo(LabelScopeInfo parent, LabelScopeKind kind) { Parent = parent; Kind = kind; }
private void ValidateJump(LabelScopeInfo reference) { // Assume we can do a ret/branch _opCode = _canReturn ? OpCodes.Ret : OpCodes.Br; // look for a simple jump out for (LabelScopeInfo j = reference; j != null; j = j.Parent) { if (_definitions.Contains(j)) { // found it, jump is valid! return; } if (j.Kind == LabelScopeKind.Finally || j.Kind == LabelScopeKind.Filter) { break; } if (j.Kind == LabelScopeKind.Try || j.Kind == LabelScopeKind.Catch) { _opCode = OpCodes.Leave; } } _acrossBlockJump = true; if (_definitions.Count > 1) { throw Error.AmbiguousJump(_node.Name); } // We didn't find an outward jump. Look for a jump across blocks LabelScopeInfo def = _definitions.First(); LabelScopeInfo common = Helpers.CommonNode(def, reference, b => b.Parent); // Assume we can do a ret/branch _opCode = _canReturn ? OpCodes.Ret : OpCodes.Br; // Validate that we aren't jumping across a finally for (LabelScopeInfo j = reference; j != common; j = j.Parent) { if (j.Kind == LabelScopeKind.Finally) { throw Error.ControlCannotLeaveFinally(); } if (j.Kind == LabelScopeKind.Filter) { throw Error.ControlCannotLeaveFilterTest(); } if (j.Kind == LabelScopeKind.Try || j.Kind == LabelScopeKind.Catch) { _opCode = OpCodes.Leave; } } // Valdiate that we aren't jumping into a catch or an expression for (LabelScopeInfo j = def; j != common; j = j.Parent) { if (!j.CanJumpInto) { if (j.Kind == LabelScopeKind.Expression) { throw Error.ControlCannotEnterExpression(); } else { throw Error.ControlCannotEnterTry(); } } } }
internal LabelScopeInfo(LabelScopeInfo parent, LabelScopeKind kind) { this.Parent = parent; this.Kind = kind; }
private void ValidateJump(LabelScopeInfo reference) { // Assume we can do a ret/branch _opCode = CanReturn ? OpCodes.Ret : OpCodes.Br; // look for a simple jump out foreach (var j in SequenceHelper.ExploreSequenceUntilNull(reference, found => found.Parent)) { if (_definitions.Contains(j)) { // found it, jump is valid! return; } if (j.Kind == LabelScopeKind.Finally || j.Kind == LabelScopeKind.Filter) { break; } if (j.Kind == LabelScopeKind.Try || j.Kind == LabelScopeKind.Catch) { _opCode = OpCodes.Leave; } } _acrossBlockJump = true; if (_node != null && _node.Type != typeof(void)) { throw new InvalidOperationException($"Cannot jump to non-local label '{_node.Name}' with a value. Only jumps to labels defined in outer blocks can pass values."); } if (_definitions.Count > 1) { throw new InvalidOperationException($"Cannot jump to ambiguous label '{_node?.Name}'."); } // We didn't find an outward jump. Look for a jump across blocks var def = _definitions.First(); var common = SequenceHelper.CommonNode(def, reference, b => b.Parent); // Assume we can do a ret/branch _opCode = CanReturn ? OpCodes.Ret : OpCodes.Br; // Validate that we aren't jumping across a finally foreach (var j in SequenceHelper.ExploreSequenceUntilNull(reference, common, found => found.Parent !)) { switch (j.Kind) { case LabelScopeKind.Finally: throw new InvalidOperationException("Control cannot leave a finally block."); case LabelScopeKind.Filter: throw new InvalidOperationException("Control cannot leave a filter test."); case LabelScopeKind.Try: case LabelScopeKind.Catch: _opCode = OpCodes.Leave; break; default: break; } } // Validate that we aren't jumping into a catch or an expression foreach (var j in SequenceHelper.ExploreSequenceUntilNull(def, found => found.Parent)) { if (j.CanJumpInto) { continue; } if (j.Kind == LabelScopeKind.Expression) { throw new InvalidOperationException("Control cannot enter an expression--only statements can be jumped into."); } throw new InvalidOperationException("Control cannot enter a try block."); } }
private void ValidateJump(LabelScopeInfo reference) { // Assume we can do a ret/branch _opCode = _canReturn ? OpCodes.Ret : OpCodes.Br; // look for a simple jump out for (LabelScopeInfo j = reference; j != null; j = j.Parent) { if (_definitions.Contains(j)) { // found it, jump is valid! return; } if (j.Kind == LabelScopeKind.Finally || j.Kind == LabelScopeKind.Filter) { break; } if (j.Kind == LabelScopeKind.Try || j.Kind == LabelScopeKind.Catch) { _opCode = OpCodes.Leave; } } _acrossBlockJump = true; if (_node != null && _node.Type != typeof(void)) { throw Error.NonLocalJumpWithValue(_node.Name); } if (_definitions.Count > 1) { throw Error.AmbiguousJump(_node.Name); } // We didn't find an outward jump. Look for a jump across blocks LabelScopeInfo def = _definitions.First(); LabelScopeInfo common = Helpers.CommonNode(def, reference, b => b.Parent); // Assume we can do a ret/branch _opCode = _canReturn ? OpCodes.Ret : OpCodes.Br; // Validate that we aren't jumping across a finally for (LabelScopeInfo j = reference; j != common; j = j.Parent) { if (j.Kind == LabelScopeKind.Finally) { throw Error.ControlCannotLeaveFinally(); } if (j.Kind == LabelScopeKind.Filter) { throw Error.ControlCannotLeaveFilterTest(); } if (j.Kind == LabelScopeKind.Try || j.Kind == LabelScopeKind.Catch) { _opCode = OpCodes.Leave; } } // Validate that we aren't jumping into a catch or an expression for (LabelScopeInfo j = def; j != common; j = j.Parent) { if (!j.CanJumpInto) { if (j.Kind == LabelScopeKind.Expression) { throw Error.ControlCannotEnterExpression(); } else { throw Error.ControlCannotEnterTry(); } } } }
private void EmitTryExpression(Expression expr) { var node = (TryExpression)expr; CheckTry(_labelBlock); //****************************************************************** // 1. ENTERING TRY //****************************************************************** var parent = _labelBlock; _labelBlock = new LabelScopeInfo(parent, LabelScopeKind.Try); IL.BeginExceptionBlock(); //****************************************************************** // 2. Emit the try statement body //****************************************************************** EmitExpression(node.Body); var tryType = node.Type; LocalBuilder?value = null; if (tryType != typeof(void)) { //store the value of the try body value = GetLocal(tryType); IL.Emit(OpCodes.Stloc, value); } //****************************************************************** // 3. Emit the catch blocks //****************************************************************** foreach (var cb in node.Handlers) { var tmpParent = _labelBlock; _labelBlock = new LabelScopeInfo(tmpParent, LabelScopeKind.Catch); // Begin the strongly typed exception block if (cb.Filter == null) { IL.BeginCatchBlock(cb.Test); } else { IL.BeginExceptFilterBlock(); } var innerScopeInfo = GetInnerScope(node, _scope); if (innerScopeInfo.HasValue) { _scope = innerScopeInfo.Value.child.Enter(this, innerScopeInfo.Value.parent); } EmitCatchStart(cb); // // Emit the catch block body // EmitExpression(cb.Body); if (tryType != typeof(void)) { //store the value of the catch block body // ReSharper disable once AssignNullToNotNullAttribute IL.Emit(OpCodes.Stloc, value); } if (innerScopeInfo.HasValue) { innerScopeInfo.Value.child.Exit(); _scope = innerScopeInfo.Value.parent; } _labelBlock = tmpParent; } //****************************************************************** // 4. Emit the finally block //****************************************************************** if (node.Finally != null || node.Fault != null) { var tmpParent = _labelBlock; _labelBlock = new LabelScopeInfo(tmpParent, LabelScopeKind.Finally); if (node.Finally != null) { IL.BeginFinallyBlock(); } else { IL.BeginFaultBlock(); } // Emit the body EmitExpressionAsVoid(node.Finally ?? node.Fault !); IL.EndExceptionBlock(); _labelBlock = tmpParent; } else { IL.EndExceptionBlock(); } if (value != null) { // ReSharper disable once AssignNullToNotNullAttribute IL.Emit(OpCodes.Ldloc, value); FreeLocal(value); } _labelBlock = parent; }