internal static Local ExtractPreamble(Method m, ContractNodes contractNodes, Block contractInitializer, out Block postPreamble, ref StackDepthTracker dupStackTracker, bool isVB) { postPreamble = null; if (m == null || m.Body == null || m.Body.Statements == null || !(m.Body.Statements.Count > 0)) return null; Block firstBlock = m.Body.Statements[0] as Block; if (firstBlock == null) return null; Block preambleBlock = new PreambleBlock(new StatementList(0)); Local result; int nextInstructionInFirstBlock = ExtractClosureInitializationAndLocalThis(m, firstBlock, contractNodes, contractInitializer, preambleBlock, 0, out result, ref dupStackTracker); // Type (2): Base ctor or deferring ctor call if (m is InstanceInitializer) { int i, j; bool found; // Type (2a): Assignments to fields of class that had initializers // Just extract the statements for (2a) as part of what gets extracted for (2b) // Type (2b): base or deferring ctor call found = SearchClump(m.Body.Statements, delegate(Statement s) { if (s == null) return false; ExpressionStatement es = s as ExpressionStatement; if (es == null) return false; MethodCall mc = es.Expression as MethodCall; if (mc == null) return false; MemberBinding mb = mc.Callee as MemberBinding; if (mb == null) return false; Method callee = mb.BoundMember as Method; // can't depend on the TargetObject being "this": it could be "pop" if // the arguments to the call had any control flow // e.g., a boolean short-circuit expression or a ternary expression. //if (mb.TargetObject is This && callee is InstanceInitializer) return true; if (callee is InstanceInitializer && ((callee.DeclaringType == m.DeclaringType || callee.DeclaringType.Template == m.DeclaringType) // deferring ctor call || (callee.DeclaringType == m.DeclaringType.BaseType || // base ctor call callee.DeclaringType.Template == m.DeclaringType.BaseType.Template))) { return true; } return false; }, out i, out j); if (found) { // for VB constructors and C# constructors with closures, we need to extend the scope to include field initializers Block b = (Block) m.Body.Statements[i]; int k = j + 1; Local extraClosureLocal = null; int stackDepth = 0; for (; k < b.Statements.Count; k++) { // skip over auxiliary closure creation if (extraClosureLocal == null && IsClosureCreation(m, b.Statements[k], out extraClosureLocal)) { continue; } switch (b.Statements[k].NodeType) { case NodeType.Nop: continue; case NodeType.AssignmentStatement: { AssignmentStatement assgmt = (AssignmentStatement) b.Statements[k]; MemberBinding mb = assgmt.Target as MemberBinding; if (mb == null) { // we might be initializing locals for default values etc if (stackDepth > 0) continue; goto doneWithFieldInits; } if (!(mb.BoundMember is Field)) { // we might be initializing locals for default values etc if (stackDepth > 0) continue; goto doneWithFieldInits; } if (mb.TargetObject == null) { // we might be initializing locals for default values etc if (stackDepth > 0) continue; goto doneWithFieldInits; } // there are 2 cases here. Either we are assigning to this.f = ..., which is a field initialization, // or it is initializing the closure field for "this" with this. if (mb.TargetObject.NodeType == NodeType.This) { // okay field initialization continue; } if (mb.TargetObject.NodeType == NodeType.Pop) { // okay field initialization (popping this) stackDepth--; continue; } // we might also be initializing the extra closure fields, if the target is the extraClosureLocal. if (mb.TargetObject == extraClosureLocal) { continue; } if (mb.TargetObject.NodeType == NodeType.Local && assgmt.Source != null && assgmt.Source.NodeType == NodeType.This && mb.BoundMember.Name != null && (mb.BoundMember.Name.Name.EndsWith("_this") || mb.BoundMember.Name.Name == "$VB$Me")) { // closure initialization (storing this) // add it to postPreamble, since it needs to be inserted for duplicate closure object postPreamble = new Block(new StatementList(1)); postPreamble.Statements.Add((Statement) b.Statements[k].Clone()); continue; } goto doneWithFieldInits; } case NodeType.ExpressionStatement: { ExpressionStatement estmt = (ExpressionStatement) b.Statements[k]; if (estmt.Expression != null && estmt.Expression is This) { // handle special push/pop pattern occurring occasionally stackDepth++; continue; } // handle ctor calls on addresses of value types // NOTE: this could be mistakenly the first user statement. MethodCall mc = estmt.Expression as MethodCall; if (mc == null) goto doneWithFieldInits; MemberBinding mb = mc.Callee as MemberBinding; if (mb == null) goto doneWithFieldInits; if (!(mb.BoundMember is InstanceInitializer)) goto doneWithFieldInits; if (!mb.BoundMember.DeclaringType.IsValueType) goto doneWithFieldInits; if (mb.TargetObject.NodeType != NodeType.AddressOf) goto doneWithFieldInits; continue; } default: goto doneWithFieldInits; } } doneWithFieldInits: StatementList sl2 = ExtractClump(m.Body.Statements, 0, 0, i, k - 1); preambleBlock.Statements.Add(new Block(sl2)); ExtractVB_ENCCallToPreamble(m.Body.Statements[i] as Block, k, preambleBlock.Statements); } else { // for struct ctors, we may not have a "this" call // for C# constructors with closures, we need to extend the scope Block b = firstBlock; int k = nextInstructionInFirstBlock; Local extraClosureLocal = null; for (; k < b.Statements.Count; k++) { // skip over auxiliary closure creation and initialization if (extraClosureLocal == null && IsClosureCreation(m, b.Statements[k], out extraClosureLocal)) { continue; } switch (b.Statements[k].NodeType) { case NodeType.Nop: continue; case NodeType.AssignmentStatement: { AssignmentStatement assgmt = (AssignmentStatement) b.Statements[k]; MemberBinding mb = assgmt.Target as MemberBinding; if (mb == null) goto doneWithFieldInits; if (!(mb.BoundMember is Field)) goto doneWithFieldInits; if (mb.TargetObject == null) goto doneWithFieldInits; // we might be initializing the extra closure fields, if the target is the extraClosureLocal. if (mb.TargetObject == extraClosureLocal) { continue; } goto doneWithFieldInits; } default: goto doneWithFieldInits; } } doneWithFieldInits: for (int toCopy = nextInstructionInFirstBlock; toCopy < k; toCopy++) { preambleBlock.Statements.Add(firstBlock.Statements[toCopy]); firstBlock.Statements[toCopy] = null; } } } // Create a new body, add the preamble block, then all of the other blocks StatementList sl = new StatementList(m.Body.Statements.Count); sl.Add(preambleBlock); if (m.Body != null) { foreach (Block b in m.Body.Statements) { if (b != null) sl.Add(b); } } m.Body.Statements = sl; return result; }
/// <summary> /// Extract prefix code prior to contracts such as closure initialization and locals /// </summary> internal static int ExtractClosureInitializationAndLocalThis( Method m, Block firstBlock, ContractNodes contractNodes, Block contractInitializer, Block preambleBlock, int currentIndex, out Local localAliasingThis, ref StackDepthTracker dupStackTracker) { localAliasingThis = null; // Any prefix of null statements and nops should go into the preamble block // In addition, for VB structure ctors, they start with initobj this // ------------------------------------------------------------------------ Contract.Assert(currentIndex <= firstBlock.Statements.Count); while (currentIndex < firstBlock.Statements.Count) { Statement s = firstBlock.Statements[currentIndex]; if (s != null && s.NodeType != NodeType.Nop) { // check for VB "initobj this" if (!m.DeclaringType.IsValueType) break; if (!(m is InstanceInitializer)) break; AssignmentStatement initObj = s as AssignmentStatement; if (initObj == null) break; AddressDereference addrderef = initObj.Target as AddressDereference; if (addrderef == null) break; if (addrderef.Address != m.ThisParameter) break; Literal initObjArg = initObj.Source as Literal; if (initObjArg == null) break; if (initObjArg.Value != null) break; } preambleBlock.Statements.Add(s); var oldCount = firstBlock.Statements.Count; var oldStats = firstBlock.Statements; firstBlock.Statements[currentIndex] = null; Contract.Assert(oldStats == firstBlock.Statements); Contract.Assert(oldCount == firstBlock.Statements.Count); currentIndex++; Contract.Assert(currentIndex <= firstBlock.Statements.Count); } Contract.Assert(currentIndex <= firstBlock.Statements.Count); // Type (1): Closure creation and initialization // --------------------------------------------- TypeNode closureType; currentIndex = MovePastClosureInit(m, firstBlock, contractNodes, contractInitializer.Statements, preambleBlock, currentIndex, ref dupStackTracker, out closureType); // Local @this in contract classes // ------------------------------- // For contract classes, since we require that they use explicit interface implementation, // allow the single assignment statement "J this_j = this;" as part of the prelude so that // explicit casts are not needed all over the contracts. Interface iface = HelperMethods.IsContractTypeForSomeOtherType(m.DeclaringType, contractNodes) as Interface; if (iface != null && currentIndex < firstBlock.Statements.Count) { AssignmentStatement assgn = firstBlock.Statements[currentIndex] as AssignmentStatement; if (assgn != null) { if (assgn.Target is Local && assgn.Target.Type == iface && assgn.Source is This) { // we currently keep the local initializer in both "contractLocalsInitializer" for static decoding // AND in the preamble block for runtime checking code generation. localAliasingThis = (Local) assgn.Target; // contractInitializer.Statements.Add((Statement)(firstBlock.Statements[currentIndex].Clone())); // preambleBlock.Statements.Add(firstBlock.Statements[currentIndex]); firstBlock.Statements[currentIndex] = null; currentIndex++; } } } // VB hoists some other literal assignments to the start of the method. while (currentIndex < firstBlock.Statements.Count) { Statement s = firstBlock.Statements[currentIndex]; if (s != null && s.NodeType != NodeType.Nop) { // check for VB literal assignments if (!s.SourceContext.Hidden) break; AssignmentStatement litAssgn = s as AssignmentStatement; if (litAssgn == null) break; var local = litAssgn.Target as Local; if (local == null) break; if (local.Name == null) break; if (!local.Name.Name.StartsWith("VB$t_ref$L")) break; // okay, we are assigning one of these locals. Put it into the preamble. } preambleBlock.Statements.Add(s); var oldCount = firstBlock.Statements.Count; var oldStats = firstBlock.Statements; firstBlock.Statements[currentIndex] = null; Contract.Assert(oldStats == firstBlock.Statements); Contract.Assert(oldCount == firstBlock.Statements.Count); currentIndex++; Contract.Assert(currentIndex <= firstBlock.Statements.Count); } Contract.Assert(currentIndex <= firstBlock.Statements.Count); return currentIndex; }
/// <summary> /// Copies the closure initialization into the contractinitializer and preambleBlock (if non-null) /// </summary> internal static int MovePastClosureInit(Method m, Block firstBlock, ContractNodes contractNodes, StatementList contractInitializer, Block preambleBlock, int currentIndex, ref StackDepthTracker dupStackTracker, out TypeNode closureType) { int indexForClosureCreationStatement = currentIndex; closureType = null; Local introducedClosureLocal = null; while (indexForClosureCreationStatement < firstBlock.Statements.Count) { var closureCreationCandidate = firstBlock.Statements[indexForClosureCreationStatement]; closureType = IsClosureCreation(m, closureCreationCandidate); if (closureType != null) break; if (contractNodes.IsContractOrValidatorOrAbbreviatorCall(closureCreationCandidate)) { // found contracts before closure creation, so get out return currentIndex; } if (closureCreationCandidate != null && closureCreationCandidate.SourceContext.IsValid) return currentIndex; indexForClosureCreationStatement++; } if (closureType != null && indexForClosureCreationStatement < firstBlock.Statements.Count) { // then there is a set of statements to add to the preamble block // up to and including "local := new ClosureClass();" for (int i = currentIndex; i <= indexForClosureCreationStatement; i++) { if (firstBlock.Statements[i] == null) continue; if (preambleBlock != null) { preambleBlock.Statements.Add(firstBlock.Statements[i]); } Local existingClosureLocal; if (IsClosureCreation(m, firstBlock.Statements[i], out existingClosureLocal)) { if (existingClosureLocal == null) { // introduce one ExpressionStatement estmt = firstBlock.Statements[i] as ExpressionStatement; if (estmt != null) { introducedClosureLocal = new Local(closureType); contractInitializer.Add( new AssignmentStatement(introducedClosureLocal, (Expression) estmt.Expression.Clone())); } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } if (preambleBlock != null) { firstBlock.Statements[i] = null; // need to null them out so search below can be done starting at beginning of m's body } } // Some number of assignment statements of the form "local.f := f;" where "f" is a parameter // that is captured by the closure. // // Roslyn generates code as follows: // new Closure() // dup // ldarg f // stfld f // dup // ldarg q // stfld q // dup... // int endOfAssignmentsToClosureFields = indexForClosureCreationStatement + 1; for (; endOfAssignmentsToClosureFields < firstBlock.Statements.Count; endOfAssignmentsToClosureFields++) { Statement s = firstBlock.Statements[endOfAssignmentsToClosureFields]; if (s == null) continue; if (s.NodeType == NodeType.Nop) continue; if (s.SourceContext.IsValid) break; // end of closure if (s is Return) break; // Return is also an ExpressionStatement ExpressionStatement exprSt = s as ExpressionStatement; if (exprSt != null && exprSt.Expression.NodeType == NodeType.Dup) { // dup of closure node continue; } AssignmentStatement assign = s as AssignmentStatement; if (assign == null) break; MemberBinding mb = assign.Target as MemberBinding; if (mb == null) break; if (mb.TargetObject == null || (mb.TargetObject.Type != closureType && mb.TargetObject.NodeType != NodeType.Pop)) break; } if (endOfAssignmentsToClosureFields - 1 < firstBlock.Statements.Count && endOfAssignmentsToClosureFields >= 1 && IsDup(firstBlock.Statements[endOfAssignmentsToClosureFields - 1])) { endOfAssignmentsToClosureFields--; // last dup is not part of closure init } dupStackTracker = new StackDepthTracker(introducedClosureLocal); for (int i = indexForClosureCreationStatement + 1; i < endOfAssignmentsToClosureFields; i++) { var stmt = firstBlock.Statements[i]; if (stmt == null) continue; if (preambleBlock != null) { preambleBlock.Statements.Add(stmt); } if (stmt.NodeType != NodeType.Nop) { // don't add nop's to contract initializer if (dupStackTracker.IsValid) { contractInitializer.Add(dupStackTracker.Visit(stmt)); } else { contractInitializer.Add((Statement) stmt.Clone()); } } if (preambleBlock != null) { firstBlock.Statements[i] = null; // need to null them out so search below can be done starting at beginning of m's body } } currentIndex = endOfAssignmentsToClosureFields; } return currentIndex; }
/// <summary> /// Copies the closure initialization into the contractinitializer and preambleBlock (if non-null) /// </summary> internal static int MovePastClosureInit(Method m, Block firstBlock, ContractNodes contractNodes, StatementList contractInitializer, Block preambleBlock, int currentIndex, ref StackDepthTracker dupStackTracker, out TypeNode closureType) { int indexForClosureCreationStatement = currentIndex; closureType = null; Local introducedClosureLocal = null; while (indexForClosureCreationStatement < firstBlock.Statements.Count) { var closureCreationCandidate = firstBlock.Statements[indexForClosureCreationStatement]; closureType = IsClosureCreation(m, closureCreationCandidate); if (closureType != null) break; if (contractNodes.IsContractOrValidatorOrAbbreviatorCall(closureCreationCandidate)) { // found contracts before closure creation, so get out return currentIndex; } // Following code was changed from simple check that closureCreationCandidate is not null and sourceContext is valid // to method call IsBaseConstructorCall. // Here is a history of this fix. // Original code led to NullReferenceException for constructor with capturing lambda // and field-like initializer compiled with VS2015. // // With VS2015 order of initialization was changed. Consider constructor initialization: // VS2013: // 1. Field-like initialization // 2. Closure creation // 3. Base class constructor call // 4. Constructor body // VS2015 // 1. Closure creation // 2. Field-like initialization // 3. Base class constructor call // 4. Constructor body // // Previous version of the code worked incorrectly with VS2015 compiler. // Because field-like initializer satisfy original criteria the function // was unable to find closure initializer properly. if (IsChainConstructorCall(closureCreationCandidate)) { return currentIndex; } indexForClosureCreationStatement++; } if (closureType != null && indexForClosureCreationStatement < firstBlock.Statements.Count) { // then there is a set of statements to add to the preamble block // up to and including "local := new ClosureClass();" for (int i = currentIndex; i <= indexForClosureCreationStatement; i++) { if (firstBlock.Statements[i] == null) continue; if (preambleBlock != null) { preambleBlock.Statements.Add(firstBlock.Statements[i]); } Local existingClosureLocal; if (IsClosureCreation(m, firstBlock.Statements[i], out existingClosureLocal)) { if (existingClosureLocal == null) { // introduce one ExpressionStatement estmt = firstBlock.Statements[i] as ExpressionStatement; if (estmt != null) { introducedClosureLocal = new Local(closureType); contractInitializer.Add( new AssignmentStatement(introducedClosureLocal, (Expression) estmt.Expression.Clone())); } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } if (preambleBlock != null) { firstBlock.Statements[i] = null; // need to null them out so search below can be done starting at beginning of m's body } } // Some number of assignment statements of the form "local.f := f;" where "f" is a parameter // that is captured by the closure. // // Roslyn generates code as follows: // new Closure() // dup // ldarg f // stfld f // dup // ldarg q // stfld q // dup... // int endOfAssignmentsToClosureFields = indexForClosureCreationStatement + 1; for (; endOfAssignmentsToClosureFields < firstBlock.Statements.Count; endOfAssignmentsToClosureFields++) { Statement s = firstBlock.Statements[endOfAssignmentsToClosureFields]; if (s == null) continue; if (s.NodeType == NodeType.Nop) continue; if (s.SourceContext.IsValid) break; // end of closure if (s is Return) break; // Return is also an ExpressionStatement ExpressionStatement exprSt = s as ExpressionStatement; if (exprSt != null && exprSt.Expression.NodeType == NodeType.Dup) { // dup of closure node continue; } AssignmentStatement assign = s as AssignmentStatement; if (assign == null) break; MemberBinding mb = assign.Target as MemberBinding; if (mb == null) break; if (mb.TargetObject == null || (mb.TargetObject.Type != closureType && mb.TargetObject.NodeType != NodeType.Pop)) break; } if (endOfAssignmentsToClosureFields - 1 < firstBlock.Statements.Count && endOfAssignmentsToClosureFields >= 1 && IsDup(firstBlock.Statements[endOfAssignmentsToClosureFields - 1])) { endOfAssignmentsToClosureFields--; // last dup is not part of closure init } dupStackTracker = new StackDepthTracker(introducedClosureLocal); for (int i = indexForClosureCreationStatement + 1; i < endOfAssignmentsToClosureFields; i++) { var stmt = firstBlock.Statements[i]; if (stmt == null) continue; if (preambleBlock != null) { preambleBlock.Statements.Add(stmt); } if (stmt.NodeType != NodeType.Nop) { // don't add nop's to contract initializer if (dupStackTracker.IsValid) { contractInitializer.Add(dupStackTracker.Visit(stmt)); } else { contractInitializer.Add((Statement) stmt.Clone()); } } if (preambleBlock != null) { firstBlock.Statements[i] = null; // need to null them out so search below can be done starting at beginning of m's body } } currentIndex = endOfAssignmentsToClosureFields; } return currentIndex; }