//----------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All Rights Reserved. // //----------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Cci; using Microsoft.Cci.MetadataReader; using Microsoft.Cci.MutableCodeModel; using Microsoft.Cci.Contracts; using Microsoft.Cci.ILToCodeModel; using System.Diagnostics.Contracts; using Bpl = Microsoft.Boogie; namespace BytecodeTranslator { public class Sink { public TraverserFactory Factory { get { return this.factory; } } readonly TraverserFactory factory; public Sink(IContractAwareHost host, TraverserFactory factory, HeapFactory heapFactory) { Contract.Requires(host != null); Contract.Requires(factory != null); Contract.Requires(heapFactory != null); this.host = host; this.factory = factory; var b = heapFactory.MakeHeap(this, out this.heap, out this.TranslatedProgram); // TODO: what if it returns false? if (this.TranslatedProgram == null) { this.TranslatedProgram = new Bpl.Program(); } else { foreach (var d in this.TranslatedProgram.TopLevelDeclarations) { var p = d as Bpl.Procedure; if (p != null) { this.initiallyDeclaredProcedures.Add(p.Name, p); } } } } public Heap Heap { get { return this.heap; } } readonly Heap heap; public Bpl.Variable ArrayContentsVariable { get { return this.arrayContentsVariable; } } readonly Bpl.Variable arrayContentsVariable = TranslationHelper.TempArrayContentsVar(); public Bpl.Variable ArrayLengthVariable { get { return this.arrayLengthVariable; } } readonly Bpl.Variable arrayLengthVariable = TranslationHelper.TempArrayLengthVar(); public Bpl.Variable ThisVariable = TranslationHelper.TempThisVar(); public Bpl.Variable RetVariable; public readonly string AllocationMethodName = "Alloc"; public readonly string StaticFieldFunction = "ClassRepr"; public readonly string ReferenceTypeName = "Ref"; public readonly string DelegateAddHelperName = "DelegateAddHelper"; public readonly string DelegateAddName = "DelegateAdd"; public readonly string DelegateRemoveName = "DelegateRemove"; public Bpl.Expr ReadHead(Bpl.Expr delegateReference) { return Bpl.Expr.Select(new Bpl.IdentifierExpr(delegateReference.tok, this.heap.DelegateHead), delegateReference); } public Bpl.Expr ReadNext(Bpl.Expr delegateReference, Bpl.Expr listNodeReference) { return Bpl.Expr.Select(Bpl.Expr.Select(new Bpl.IdentifierExpr(delegateReference.tok, this.heap.DelegateNext), delegateReference), listNodeReference); } public Bpl.Expr ReadMethod(Bpl.Expr delegateReference, Bpl.Expr listNodeReference) { return Bpl.Expr.Select(Bpl.Expr.Select(new Bpl.IdentifierExpr(delegateReference.tok, this.heap.DelegateMethod), delegateReference), listNodeReference); } public Bpl.Expr ReadReceiver(Bpl.Expr delegateReference, Bpl.Expr listNodeReference) { return Bpl.Expr.Select(Bpl.Expr.Select(new Bpl.IdentifierExpr(delegateReference.tok, this.heap.DelegateReceiver), delegateReference), listNodeReference); } public readonly Bpl.Program TranslatedProgram; /// /// Creates a fresh local var of the given Type and adds it to the /// Bpl Implementation /// /// The type of the new variable /// A fresh Variable with automatic generated name and location public Bpl.Variable CreateFreshLocal(ITypeReference typeReference) { Bpl.IToken loc = Bpl.Token.NoToken; // Helper Variables do not have a location Bpl.Type t = TranslationHelper.CciTypeToBoogie(typeReference); Bpl.LocalVariable v = new Bpl.LocalVariable(loc, new Bpl.TypedIdent(loc, TranslationHelper.GenerateTempVarName(), t)); ILocalDefinition dummy = new LocalDefinition(); // Creates a dummy entry for the Dict, since only locals in the dict are translated to boogie localVarMap.Add(dummy, v); return v; } private Dictionary localVarMap = null; public Dictionary LocalVarMap { get { return this.localVarMap; } } /// /// /// /// /// public Bpl.Variable FindOrCreateLocalVariable(ILocalDefinition local) { Bpl.LocalVariable v; Bpl.IToken tok = local.Token(); Bpl.Type t = TranslationHelper.CciTypeToBoogie(local.Type.ResolvedType); if (!localVarMap.TryGetValue(local, out v)) { v = new Bpl.LocalVariable(tok, new Bpl.TypedIdent(tok, local.Name.Value, t)); localVarMap.Add(local, v); } return v; } /// /// /// /// /// STUB /// public Bpl.Variable FindParameterVariable(IParameterDefinition param, bool contractContext) { MethodParameter mp; ProcedureInfo procAndFormalMap; var sig = param.ContainingSignature; // BUGBUG: If param's signature is not a method reference, then it doesn't have an interned // key. The declaredMethods table needs to use ISignature for its keys. var key = ((IMethodReference)sig).InternedKey; this.declaredMethods.TryGetValue(key, out procAndFormalMap); var formalMap = procAndFormalMap.FormalMap; formalMap.TryGetValue(param, out mp); return contractContext ? mp.inParameterCopy : mp.outParameterCopy; } public Bpl.Variable FindOrCreateFieldVariable(IFieldReference field) { // The Heap has to decide how to represent the field (i.e., its type), // all the Sink cares about is adding a declaration for it. Bpl.Variable v; var key = field.InternedKey; if (!this.declaredFields.TryGetValue(key, out v)) { v = this.Heap.CreateFieldVariable(field); this.declaredFields.Add(key, v); this.TranslatedProgram.TopLevelDeclarations.Add(v); } return v; } /// /// The keys to the table are the interned key of the field. /// private Dictionary declaredFields = new Dictionary(); public Bpl.Variable FindOrCreateEventVariable(IEventDefinition e) { Bpl.Variable v; if (!this.declaredEvents.TryGetValue(e, out v)) { v = this.Heap.CreateEventVariable(e); this.declaredEvents.Add(e, v); this.TranslatedProgram.TopLevelDeclarations.Add(v); } return v; } private Dictionary declaredEvents = new Dictionary(); public Bpl.Variable FindOrCreatePropertyVariable(IPropertyDefinition p) { return null; } public Bpl.Constant FindOrCreateConstant(string str) { Bpl.Constant c; if (!this.declaredStringConstants.TryGetValue(str, out c)) { var tok = Bpl.Token.NoToken; var t = Bpl.Type.Int; var name = "$string_literal_" + TranslationHelper.TurnStringIntoValidIdentifier(str); var tident = new Bpl.TypedIdent(tok, name, t); c = new Bpl.Constant(tok, tident, true); this.declaredStringConstants.Add(str, c); this.TranslatedProgram.TopLevelDeclarations.Add(c); } return c; } private Dictionary declaredStringConstants = new Dictionary(); private Dictionary declaredProperties = new Dictionary(); public struct ProcedureInfo { private Bpl.DeclWithFormals decl; private Dictionary formalMap; private Bpl.Variable returnVariable; public ProcedureInfo(Bpl.DeclWithFormals decl, Dictionary formalMap, Bpl.Variable returnVariable) { this.decl = decl; this.formalMap = formalMap; this.returnVariable = returnVariable; } public Bpl.DeclWithFormals Decl { get { return decl; } } public Dictionary FormalMap { get { return formalMap; } } public Bpl.Variable ReturnVariable { get { return returnVariable; } } } public Bpl.DeclWithFormals FindOrCreateProcedure(IMethodDefinition method) { ProcedureInfo procAndFormalMap; var key = method.InternedKey; if (!this.declaredMethods.TryGetValue(key, out procAndFormalMap)) { string MethodName = TranslationHelper.CreateUniqueMethodName(method); Bpl.Function f; Bpl.Procedure p; if (this.initiallyDeclaredProcedures.TryGetValue(MethodName, out p)) return p; #region Create in- and out-parameters int in_count = 0; int out_count = 0; MethodParameter mp; var formalMap = new Dictionary(); foreach (IParameterDefinition formal in method.Parameters) { mp = new MethodParameter(formal); if (mp.inParameterCopy != null) in_count++; if (mp.outParameterCopy != null && (formal.IsByReference || formal.IsOut)) out_count++; formalMap.Add(formal, mp); } #region Look for Returnvalue Bpl.Variable savedRetVariable = this.RetVariable; if (method.Type.TypeCode != PrimitiveTypeCode.Void) { Bpl.Type rettype = TranslationHelper.CciTypeToBoogie(method.Type); out_count++; this.RetVariable = new Bpl.Formal(method.Token(), new Bpl.TypedIdent(method.Type.Token(), "$result", rettype), false); } else { this.RetVariable = null; } #endregion Bpl.Formal/*?*/ self = null; #region Create 'this' parameter if (!method.IsStatic) { in_count++; Bpl.Type selftype = Bpl.Type.Int; self = new Bpl.Formal(method.Token(), new Bpl.TypedIdent(method.Type.Token(), "this", selftype), true); } #endregion Bpl.Variable[] invars = new Bpl.Formal[in_count]; Bpl.Variable[] outvars = new Bpl.Formal[out_count]; int i = 0; int j = 0; #region Add 'this' parameter as first in parameter if (!method.IsStatic) invars[i++] = self; #endregion foreach (MethodParameter mparam in formalMap.Values) { if (mparam.inParameterCopy != null) { invars[i++] = mparam.inParameterCopy; } if (mparam.outParameterCopy != null) { if (mparam.underlyingParameter.IsByReference || mparam.underlyingParameter.IsOut) outvars[j++] = mparam.outParameterCopy; } } #region add the returnvalue to out if there is one if (this.RetVariable != null) outvars[j] = this.RetVariable; #endregion #endregion #region Check The Method Contracts Bpl.RequiresSeq boogiePrecondition = new Bpl.RequiresSeq(); Bpl.EnsuresSeq boogiePostcondition = new Bpl.EnsuresSeq(); Bpl.IdentifierExprSeq boogieModifies = new Bpl.IdentifierExprSeq(); var possiblyUnspecializedMethod = Unspecialize(method); var contract = Microsoft.Cci.MutableContracts.ContractHelper.GetMethodContractFor(this.host, possiblyUnspecializedMethod.ResolvedMethod); if (contract != null) { try { foreach (IPrecondition pre in contract.Preconditions) { var stmtTraverser = this.factory.MakeStatementTraverser(this, null, true); ExpressionTraverser exptravers = this.factory.MakeExpressionTraverser(this, stmtTraverser, true); exptravers.Visit(pre.Condition); // TODO // Todo: Deal with Descriptions var req = new Bpl.Requires(pre.Token(), false, exptravers.TranslatedExpressions.Pop(), ""); boogiePrecondition.Add(req); } foreach (IPostcondition post in contract.Postconditions) { var stmtTraverser = this.factory.MakeStatementTraverser(this, null, true); ExpressionTraverser exptravers = this.factory.MakeExpressionTraverser(this, stmtTraverser, true); exptravers.Visit(post.Condition); // Todo: Deal with Descriptions var ens = new Bpl.Ensures(post.Token(), false, exptravers.TranslatedExpressions.Pop(), ""); boogiePostcondition.Add(ens); } foreach (IAddressableExpression mod in contract.ModifiedVariables) { ExpressionTraverser exptravers = this.factory.MakeExpressionTraverser(this, null, true); exptravers.Visit(mod); Bpl.IdentifierExpr idexp = exptravers.TranslatedExpressions.Pop() as Bpl.IdentifierExpr; if (idexp == null) { throw new TranslationException(String.Format("Cannot create IdentifierExpr for Modifyed Variable {0}", mod.ToString())); } boogieModifies.Add(idexp); } } catch (TranslationException te) { throw new NotImplementedException("Cannot Handle Errors in Method Contract: " + te.ToString()); } catch { throw; } } #endregion Bpl.DeclWithFormals decl; if (IsPure(method)) { var func = new Bpl.Function(method.Token(), MethodName, new Bpl.VariableSeq(invars), this.RetVariable); decl = func; } else { var proc = new Bpl.Procedure(method.Token(), MethodName, new Bpl.TypeVariableSeq(), new Bpl.VariableSeq(invars), new Bpl.VariableSeq(outvars), boogiePrecondition, boogieModifies, boogiePostcondition); decl = proc; } string newName = null; if (IsStubMethod(method, out newName)) { if (newName != null) { decl.Name = newName; } } else { this.TranslatedProgram.TopLevelDeclarations.Add(decl); } procAndFormalMap = new ProcedureInfo(decl, formalMap, this.RetVariable); this.declaredMethods.Add(key, procAndFormalMap); this.RetVariable = savedRetVariable; } return procAndFormalMap.Decl; } // TODO: Fix test to return true iff method is marked with the "real" [Pure] attribute // also, should it return true for properties and all of the other things the tools // consider pure? private bool IsPure(IMethodDefinition method) { foreach (var a in method.Attributes) { if (TypeHelper.GetTypeName(a.Type).EndsWith("PureAttribute")) { return true; } } return false; } // TODO: check method's containing type in case the entire type is a stub type. // TODO: do a type test, not a string test for the attribute private bool IsStubMethod(IMethodReference method, out string/*?*/ newName) { newName = null; var methodDefinition = method.ResolvedMethod; foreach (var a in methodDefinition.Attributes) { if (TypeHelper.GetTypeName(a.Type).EndsWith("StubAttribute")) { foreach (var c in a.Arguments) { var mdc = c as IMetadataConstant; if (mdc != null && mdc.Type.TypeCode == PrimitiveTypeCode.String) { newName = (string) (mdc.Value); break; } } return true; } } return false; } public ProcedureInfo FindOrCreateProcedureAndReturnProcAndFormalMap(IMethodDefinition method) { this.FindOrCreateProcedure(method); return this.declaredMethods[method.InternedKey]; } private static IMethodReference Unspecialize(IMethodReference method) { IMethodReference result = method; var gmir = result as IGenericMethodInstanceReference; if (gmir != null) { result = gmir.GenericMethod; } var smr = result as ISpecializedMethodReference; if (smr != null) { result = smr.UnspecializedVersion; } // Temporary hack until ISpecializedMethodDefinition implements ISpecializedMethodReference var smd = result as ISpecializedMethodDefinition; if (smd != null) { result = smd.UnspecializedVersion; } return result; } /// /// Creates a fresh variable that represents the type of /// in the Bpl program. I.e., its /// value represents the expression "typeof(type)". /// public Bpl.Variable FindOrCreateType(ITypeReference type) { // The Heap has to decide how to represent the field (i.e., its type), // all the Sink cares about is adding a declaration for it. Bpl.Variable t; var key = type.InternedKey; if (!this.declaredTypes.TryGetValue(key, out t)) { t = this.Heap.CreateTypeVariable(type); this.declaredTypes.Add(key, t); this.TranslatedProgram.TopLevelDeclarations.Add(t); } return t; } /// /// The keys to the table are the interned key of the type. /// private Dictionary declaredTypes = new Dictionary(); /// /// The keys to the table are the interned keys of the methods. /// The values are pairs: first element is the procedure, /// second element is the formal map for the procedure /// private Dictionary declaredMethods = new Dictionary(); /// /// The values in this table are the procedures /// defined in the program created by the heap in the Sink's ctor. /// private Dictionary initiallyDeclaredProcedures = new Dictionary(); public void BeginMethod() { this.localVarMap = new Dictionary(); } public Dictionary> delegateTypeToDelegates = new Dictionary>(); public void AddDelegate(ITypeDefinition type, IMethodDefinition defn) { if (!delegateTypeToDelegates.ContainsKey(type)) delegateTypeToDelegates[type] = new HashSet(); delegateTypeToDelegates[type].Add(defn); } public void AddDelegateType(ITypeDefinition type) { if (!delegateTypeToDelegates.ContainsKey(type)) delegateTypeToDelegates[type] = new HashSet(); } private Dictionary delegateMethods = new Dictionary(); private IContractAwareHost host; public Bpl.Constant FindOrAddDelegateMethodConstant(IMethodDefinition defn) { if (delegateMethods.ContainsKey(defn)) return delegateMethods[defn]; string methodName = TranslationHelper.CreateUniqueMethodName(defn); var typedIdent = new Bpl.TypedIdent(Bpl.Token.NoToken, methodName, Bpl.Type.Int); var constant = new Bpl.Constant(Bpl.Token.NoToken, typedIdent, true); this.TranslatedProgram.TopLevelDeclarations.Add(constant); delegateMethods[defn] = constant; return constant; } } }