// Ensures: // Preconditions != null // && ForAll{Requires r in Preconditions; // r.Condition is BlockExpression // && r.Condition.Type == Void // && IsClump(r.Condition.Block.Statements) // Postconditions != null // && ForAll{Ensures e in Posconditions; // e.PostCondition is BlockExpression // && e.PostCondition.Type == Void // && IsClump(e.PostCondition.Block.Statements) // (In addition, each Requires is a RequiresPlain when the contract // call is Contract.Requires and is a RequiresOtherwise when the // contract call is Critical.Requires. In the latter case, the // ThrowException is filled in correctly.) // /// <summary> /// /// </summary> /// <param name="method"></param> /// <param name="Preconditions"></param> /// <param name="Postconditions"></param> /// <param name="Validations"></param> /// <param name="contractInitializerBlock">used to store extra closure initializations from abbrevs and validators</param> public void CheapAndDirty( Method method, ref RequiresList Preconditions, ref EnsuresList Postconditions, ref RequiresList Validations, ref EnsuresList modelPostConditions, Block contractInitializerBlock, ref HelperMethods.StackDepthTracker dupStackTracker) { if (this.verbose) { Console.WriteLine("Method : " + method.FullName); } if (method == null || method.Body == null || method.Body.Statements == null || method.Body.Statements.Count <= 0) { return; } Block methodBody = method.Body; int n = methodBody.Statements.Count; int beginning = 0; while (beginning < n && methodBody.Statements[beginning] is PreambleBlock) { beginning++; } int lastBlockContainingContract; int lastStatementContainingContract; bool anyContractCall = FindLastBlockWithContracts(methodBody.Statements, beginning, out lastBlockContainingContract, out lastStatementContainingContract); // 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(); SourceContext lastContractSourceContext = method.SourceContext; if (!anyContractCall) { if (this.verbose) { Console.WriteLine("\tNo contracts found"); } // still need to check for bad other contract calls in method body goto CheckBody; } Block lastBlock = methodBody.Statements[lastBlockContainingContract] as Block; lastContractSourceContext = lastBlock.SourceContext; // probably not a good context, what to do if one can't be found? if (lastBlock.Statements != null && 0 <= lastStatementContainingContract && lastStatementContainingContract < lastBlock.Statements.Count) { lastContractSourceContext = lastBlock.Statements[lastStatementContainingContract].SourceContext; } // Make sure contract section is not in any try-catch region TrivialHashtable<int> block2Index = new TrivialHashtable<int>(methodBody.Statements.Count); for (int i = 0, nn = methodBody.Statements.Count; i < nn; i++) { if (methodBody.Statements[i] == null) continue; block2Index[methodBody.Statements[i].UniqueKey] = i; } // Check each exception handler and see if any overlap with the contract section for (int i = 0, nn = method.ExceptionHandlers == null ? 0 : method.ExceptionHandlers.Count; i < nn; i++) { ExceptionHandler eh = method.ExceptionHandlers[i]; if (eh == null) continue; if (((int) block2Index[eh.BlockAfterTryEnd.UniqueKey]) < beginning || lastBlockContainingContract < ((int) block2Index[eh.TryStartBlock.UniqueKey])) { continue; // can't overlap } this.HandleError(method, 1024, "Contract section within try block.", lastContractSourceContext); return; } // Extract <beginning,0> to <lastBlockContainingContract,lastStatmentContainingContract> StatementList contractClump = HelperMethods.ExtractClump(methodBody.Statements, beginning, 0, lastBlockContainingContract, lastStatementContainingContract); // Look for bad stuff BadStuff(method, contractClump, lastContractSourceContext); // Make sure that the entire contract section is closed. if (!CheckClump(method, gatherLocals, currentMethodSourceContext, new Block(contractClump))) { 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(); Preconditions = new RequiresList(); Postconditions = new EnsuresList(); Validations = new RequiresList(); modelPostConditions = new EnsuresList(); if (!ExtractFromClump( contractClump, method, gatherLocals, Preconditions, Postconditions, Validations, modelPostConditions, lastContractSourceContext, method, contractInitializerBlock, ref dupStackTracker)) { return; } CheckBody: // Check "real" method body for use of any locals used in contracts // var checkMethodBody = new CheckForBadContractStuffInMethodBody(gatherLocals, this.CallErrorFound, method); var checkMethodBody = new CheckLocals(gatherLocals); checkMethodBody.Visit(methodBody); if (!this.fSharp && checkMethodBody.reUseOfExistingLocal != null) { SourceContext sc = lastContractSourceContext; this.HandleError(method, 1025, "After contract block, found use of local variable '" + checkMethodBody.reUseOfExistingLocal.Name.Name + "' defined in contract block", sc); } }
private bool CheckClump(Method method, GatherLocals gatherLocals, SourceContext sctx, Block clump) { if (!HelperMethods.ClumpIsClosed(clump.Statements)) { this.contractNodes.CallErrorFound(new Error(1017, "Malformed contract section in method '" + method.FullName + "'", sctx)); return false; // throw new ExtractorException(); } TypeNode t = method.DeclaringType; bool IsContractTypeForSomeOtherType = t == null ? false : HelperMethods.GetTypeFromAttribute(t, ContractNodes.ContractClassForAttributeName) != null; // We require that a contract class implement the interface it is holding contracts for. In addition, the methods in that class must be explicit // interface implementations. This creates a problem when the contracts need to refer to other methods in the interface. The "this" reference has // the wrong type: it needs to be the interface type. Writing an explicit cast before every reference is painful. It is better to allow the user // to create a local variable of the interface type and assign it before the contracts. This can't cause a problem with the local being used later // in the method body, because methods in contract classes don't have method bodies, just contracts. So don't check for re-use of locals for such // methods. // // Example of above comment //[ContractClass(typeof(ContractForJ))] //interface J{ // bool M(int x); // [Pure] // bool P { get; } //} //[ContractClassFor(typeof(J))] //class ContractForJ : J{ // bool J.M(int x) { // J jThis = this; // Contract.Requires(x != 3); // Contract.Requires(jThis.P); // Contract.Ensures(x != 5 || !jThis.P); // return default(bool); // } // bool J.P { // get { // Contract.Ensures(Contract.Result<bool>()); // return default(bool); // } // } //} if (!IsContractTypeForSomeOtherType) { // First, make sure the clump doesn't use any locals that were used // in any previous contract, need a new instance each time CheckLocals checkLocals = new CheckLocals(gatherLocals); checkLocals.Visit(clump); if (!this.fSharp && checkLocals.reUseOfExistingLocal != null) { this.HandleError(method, 1040, "Reuse of existing local variable '" + checkLocals.reUseOfExistingLocal.Name.Name + "' in contract.", sctx); return false; } // If that test passes, then add in the locals used in the clump into the table of locals that have been used gatherLocals.Visit(clump); } return true; }