private void ExtractContractsForMethod(Method method, object dummy) { Contract.Requires(method != null); RequiresList preconditions = null; EnsuresList postconditions = null; RequiresList validations = null; EnsuresList modelPostconditions = null; //Console.WriteLine("Extracting contract for method {0}", method.FullName); // set its contract so pure is properly handled. var methodContract = method.Contract = new MethodContract(method); try { if (method.IsAbstract /* && contractClass == null */) { // Abstract methods cannot have a body, so nothing to extract return; } TypeNode /*?*/ closure = null; bool possiblyAsync; if (IsIteratorOrAsyncMethodCandidate(method, out possiblyAsync)) { closure = FindClosureClass(method); if (closure != null) { this.ProcessClosureClass(method, closure, possiblyAsync); return; } } if (method.Body == null || method.Body.Statements == null) return; // Find first source context, use it if no better one is found on errors // if (method.Body != null && method.Body.Statements != null) { if (this.verbose) { Console.WriteLine(method.FullName); } bool found = false; for (int i = 0, n = method.Body.Statements.Count; i < n && !found; i++) { Block b = method.Body.Statements[i] as Block; if (b != null) { for (int j2 = 0, m = b.Statements == null ? 0 : b.Statements.Count; j2 < m; j2++) { Contract.Assert(m == b.Statements.Count, "loop invariant not inferred"); Statement s = b.Statements[j2]; if (s != null) { SourceContext sctx = s.SourceContext; if (sctx.IsValid) { found = true; // s.SourceContext = new SourceContext(); // wipe out the source context because this statement will no longer be the first one in the method body if there are any contract calls this.currentMethodSourceContext = sctx; if (this.verbose) { Console.WriteLine("block {0}, statement {1}: ({2},{3})", i, j2, sctx.StartLine, sctx.StartColumn); } break; } } } } } } Block contractInitializerBlock = new Block(new StatementList()); Block postPreamble = null; HelperMethods.StackDepthTracker dupStackTracker = new HelperMethods.StackDepthTracker(); var contractLocalAliasingThis = HelperMethods.ExtractPreamble(method, this.contractNodes, contractInitializerBlock, out postPreamble, ref dupStackTracker, this.isVB); bool saveErrorFound = this.errorFound; this.errorFound = false; // Extract pre- and postconditions if (method.Body != null && method.Body.Statements != null) { this.CheapAndDirty(method, ref preconditions, ref postconditions, ref validations, ref modelPostconditions, contractInitializerBlock, ref dupStackTracker); if (this.errorFound) { method.ClearBody(); this.errorFound = saveErrorFound; return; } this.errorFound = saveErrorFound; } // Sanitize contract by renaming local aliasing "this" to This if (contractLocalAliasingThis != null && method.ThisParameter != null) { var renamer = new ContractLocalToThis(contractLocalAliasingThis, method.ThisParameter); renamer.Visit(preconditions); renamer.Visit(postconditions); renamer.Visit(modelPostconditions); } // Split out async ensures var asyncPostconditions = SplitAsyncEnsures(ref postconditions, method); // Store contracts into the appropriate slots Contract.Assume(methodContract.RequiresCount == 0); Contract.Assume(methodContract.EnsuresCount == 0); Contract.Assume(methodContract.ModelEnsuresCount == 0); methodContract.ContractInitializer = contractInitializerBlock; methodContract.PostPreamble = postPreamble; methodContract.Requires = preconditions; methodContract.Ensures = postconditions; methodContract.AsyncEnsures = asyncPostconditions; methodContract.ModelEnsures = modelPostconditions; methodContract.Validations = validations; return; } catch (NotImplementedException ni) { // indicates a problem this.HandleError(method, 1099, "Contract extraction failed: " + ni.Message, default(SourceContext)); } finally { // normalize contract by forcing IsPure to look at attributes and removing contract it is empty var isPure = methodContract.IsPure; if (!isPure && methodContract.RequiresCount == 0 && methodContract.EnsuresCount == 0 && methodContract.ModelEnsuresCount == 0 && methodContract.AsyncEnsuresCount == 0 && methodContract.ValidationsCount == 0) { method.Contract = null; } else { // turn helper method calls to Result, OldValue, ValueAtReturn into proper AST nodes. this.extractionFinalizer.VisitMethodContract(methodContract); } } }
private void ProcessClosureClass(Method method, TypeNode closure, bool isAsync) { Contract.Requires(method != null); Contract.Requires(closure != null); Method movenext = closure.GetMethod(StandardIds.MoveNext); if (movenext == null) return; movenext.IsAsync = isAsync; if (movenext.Body == null) return; if (movenext.Body.Statements == null) return; SourceContext defaultSourceContext; Block contractInitializerBlock = new Block(new StatementList()); HelperMethods.StackDepthTracker dupStackTracker = new HelperMethods.StackDepthTracker(); AssumeBlock originalContractPosition; StatementList contractClump = GetContractClumpFromMoveNext(method, movenext, contractNodes, contractInitializerBlock.Statements, out defaultSourceContext, ref dupStackTracker, out originalContractPosition); if (contractClump != null) { // Look for bad stuff BadStuff(method, contractClump, defaultSourceContext); // Make sure any locals in the contracts are disjoint from the locals in the rest of the body // can use the same one throughout GatherLocals gatherLocals = new GatherLocals(); // Make sure that the entire contract section is closed. if (!CheckClump(movenext, gatherLocals, currentMethodSourceContext, new Block(contractClump))) { movenext.ClearBody(); return; } // Checking that had the side effect of populating the hashtable, but now each contract will be individually visited. // That process needs to start with a fresh table. gatherLocals.Locals = new TrivialHashtable(); RequiresList Preconditions = new RequiresList(); EnsuresList Postconditions = new EnsuresList(); RequiresList Validations = new RequiresList(); EnsuresList modelPostconditions = new EnsuresList(); EnsuresList asyncPostconditions = null; // REVIEW: What should we do with the Validations in this case? Should we map them to the enumerator method? Maybe not, since without // rewriting this won't happen. if (!ExtractFromClump(contractClump, movenext, gatherLocals, Preconditions, Postconditions, Validations, modelPostconditions, defaultSourceContext, method, contractInitializerBlock, ref dupStackTracker)) { movenext.ClearBody(); return; } if (isAsync) { asyncPostconditions = SplitAsyncEnsures(ref Postconditions, method); } try { // Next is to attach the preconditions to method (instead of movenext) // To do so, we have to duplicate the expressions and statements in Precondition, Postcondition and contractInitializerBlock Duplicator dup = new Duplicator(closure.DeclaringModule, method.DeclaringType); var origPreconditions = Preconditions; var origValidations = Validations; var origcontractInitializerBlock = contractInitializerBlock; Preconditions = dup.VisitRequiresList(Preconditions); Postconditions = dup.VisitEnsuresList(Postconditions); Validations = dup.VisitRequiresList(Validations); contractInitializerBlock = dup.VisitBlock(contractInitializerBlock); asyncPostconditions = dup.VisitEnsuresList(asyncPostconditions); var mapClosureExpToOriginal = BuildMappingFromClosureToOriginal(closure, movenext, method); Preconditions = mapClosureExpToOriginal.Apply(Preconditions); Postconditions = mapClosureExpToOriginal.Apply(Postconditions); Validations = mapClosureExpToOriginal.Apply(Validations); contractInitializerBlock = mapClosureExpToOriginal.Apply(contractInitializerBlock); asyncPostconditions = mapClosureExpToOriginal.Apply(asyncPostconditions); //MemberList members = FindClosureMembersInContract(closure, movenext); // MakeClosureAccessibleToOriginalMethod(closure, members); if (method.Contract == null) method.Contract = new MethodContract(method); method.Contract.Requires = Preconditions; method.Contract.Validations = Validations; // Postconditions are sanity checked here, because Result<T> must be compared against the // return type of the original method. It is most conveniently done after the type substitution. // TODO: refactor the checking part altogether out of ExtractFromClump. method.Contract.Ensures = Postconditions; method.Contract.ModelEnsures = modelPostconditions; method.Contract.ContractInitializer = contractInitializerBlock; method.Contract.AsyncEnsures = asyncPostconditions; // Following replacement causes some weird issues for complex preconditions (like x != null && x.Length > 0) // when CCRewriter is used with /publicsurface or Preconditions only. // This fix could be temporal and proper fix would be applied in the future. // After discussion this issue with original CC authors (Mike Barnett and Francesco Logozzo), // we decided that this fix is safe and lack of Assume statement in the MoveNext method will not affect // customers (neither CCRewriter customers nor CCCheck customers). // If this assumption would not be true in the future, proper fix should be applied. // put requires as assumes into movenext method at original position // ReplaceRequiresWithAssumeInMoveNext(origPreconditions, originalContractPosition); // no postPreamble to initialize, as method is not a ctor } finally { // this is done in caller!!! //// normalize contract by forcing IsPure to look at attributes and removing contract it is empty //var contract = method.Contract; //var isPure = contract.IsPure; //if (!isPure && contract.RequiresCount == 0 && contract.EnsuresCount == 0 && contract.ModelEnsuresCount == 0 && contract.ValidationsCount == 0 && contract.AsyncEnsuresCount == 0) //{ // method.Contract = null; //} else //{ // // turn helper method calls to Result, OldValue, ValueAtReturn into proper AST nodes. // this.extractionFinalizer.VisitMethodContract(method.Contract); //} } } }