// // flowanalyis.cs: The control flow analysis code // // Authors: // Martin Baulig (martin@ximian.com) // Raja R Harinath (rharinath@novell.com) // Marek Safar (marek.safar@gmail.com) // // Copyright 2001, 2002, 2003 Ximian, Inc. // Copyright 2003-2008 Novell, Inc. // Copyright 2011 Xamarin, Inc. // using System; using System.Text; using System.Collections.Generic; namespace Mono.CSharp { // // This is used by the flow analysis code to keep track of the type of local variables. // // The flow code uses a BitVector to keep track of whether a variable has been assigned // or not. This is easy for fundamental types (int, char etc.) or reference types since // you can only assign the whole variable as such. // // For structs, we also need to keep track of all its fields. To do this, we allocate one // bit for the struct itself (it's used if you assign/access the whole struct) followed by // one bit for each of its fields. // // This class computes this `layout' for each type. // public class TypeInfo { // // Total number of bits a variable of this type consumes in the flow vector. // public readonly int TotalLength; // // Number of bits the simple fields of a variable of this type consume // in the flow vector. // public readonly int Length; // // This is only used by sub-structs. // public readonly int Offset; // // If this is a struct. // public readonly bool IsStruct; // // If this is a struct, all fields which are structs theirselves. // public TypeInfo[] SubStructInfo; readonly StructInfo struct_info; static readonly TypeInfo simple_type = new TypeInfo (1); static TypeInfo () { Reset (); } public static void Reset () { StructInfo.field_type_hash = new Dictionary (); } TypeInfo (int totalLength) { this.TotalLength = totalLength; } TypeInfo (StructInfo struct_info, int offset) { this.struct_info = struct_info; this.Offset = offset; this.Length = struct_info.Length; this.TotalLength = struct_info.TotalLength; this.SubStructInfo = struct_info.StructFields; this.IsStruct = true; } public int GetFieldIndex (string name) { if (struct_info == null) return 0; return struct_info [name]; } public TypeInfo GetStructField (string name) { if (struct_info == null) return null; return struct_info.GetStructField (name); } public static TypeInfo GetTypeInfo (TypeSpec type, IMemberContext context) { if (!type.IsStruct) return simple_type; TypeInfo info; Dictionary type_hash; if (type.BuiltinType > 0) { // Don't cache built-in types, they are null in most cases except for // corlib compilation when we need to distinguish between declaration // and referencing type_hash = null; } else { type_hash = context.Module.TypeInfoCache; if (type_hash.TryGetValue (type, out info)) return info; } var struct_info = StructInfo.GetStructInfo (type, context); if (struct_info != null) { info = new TypeInfo (struct_info, 0); } else { info = simple_type; } if (type_hash != null) type_hash.Add (type, info); return info; } // // A struct's constructor must always assign all fields. // This method checks whether it actually does so. // public bool IsFullyInitialized (FlowAnalysisContext fc, VariableInfo vi, Location loc) { if (struct_info == null) return true; bool ok = true; for (int i = 0; i < struct_info.Count; i++) { var field = struct_info.Fields[i]; if (!fc.IsStructFieldDefinitelyAssigned (vi, field.Name)) { var bf = field.MemberDefinition as Property.BackingFieldDeclaration; if (bf != null) { if (bf.Initializer != null) continue; fc.Report.Error (843, loc, "An automatically implemented property `{0}' must be fully assigned before control leaves the constructor. Consider calling the default struct contructor from a constructor initializer", field.GetSignatureForError ()); ok = false; continue; } fc.Report.Error (171, loc, "Field `{0}' must be fully assigned before control leaves the constructor", field.GetSignatureForError ()); ok = false; } } return ok; } public override string ToString () { return String.Format ("TypeInfo ({0}:{1}:{2})", Offset, Length, TotalLength); } class StructInfo { readonly List fields; public readonly TypeInfo[] StructFields; public readonly int Length; public readonly int TotalLength; public static Dictionary field_type_hash; private Dictionary struct_field_hash; private Dictionary field_hash; bool InTransit; // // We only need one instance per type // StructInfo (TypeSpec type, IMemberContext context) { field_type_hash.Add (type, this); fields = MemberCache.GetAllFieldsForDefiniteAssignment (type, context); struct_field_hash = new Dictionary (); field_hash = new Dictionary (fields.Count); StructFields = new TypeInfo[fields.Count]; StructInfo[] sinfo = new StructInfo[fields.Count]; InTransit = true; for (int i = 0; i < fields.Count; i++) { var field = fields [i]; if (field.MemberType.IsStruct) sinfo [i] = GetStructInfo (field.MemberType, context); if (sinfo [i] == null) field_hash.Add (field.Name, ++Length); else if (sinfo [i].InTransit) { sinfo [i] = null; return; } } InTransit = false; TotalLength = Length + 1; for (int i = 0; i < fields.Count; i++) { var field = fields [i]; if (sinfo [i] == null) continue; field_hash.Add (field.Name, TotalLength); StructFields [i] = new TypeInfo (sinfo [i], TotalLength); struct_field_hash.Add (field.Name, StructFields [i]); TotalLength += sinfo [i].TotalLength; } } public int Count { get { return fields.Count; } } public List Fields { get { return fields; } } public int this [string name] { get { int val; if (!field_hash.TryGetValue (name, out val)) return 0; return val; } } public TypeInfo GetStructField (string name) { TypeInfo ti; if (struct_field_hash.TryGetValue (name, out ti)) return ti; return null; } public static StructInfo GetStructInfo (TypeSpec type, IMemberContext context) { if (type.BuiltinType > 0 && type != context.CurrentType) return null; StructInfo info; if (field_type_hash.TryGetValue (type, out info)) return info; return new StructInfo (type, context); } } } // // This is used by definite assignment analysis code to store information about a local variable // or parameter. Depending on the variable's type, we need to allocate one or more elements // in the BitVector - if it's a fundamental or reference type, we just need to know whether // it has been assigned or not, but for structs, we need this information for each of its fields. // public class VariableInfo { readonly string Name; readonly TypeInfo TypeInfo; // // The bit offset of this variable in the flow vector. // readonly int Offset; // // The number of bits this variable needs in the flow vector. // The first bit always specifies whether the variable as such has been assigned while // the remaining bits contain this information for each of a struct's fields. // readonly int Length; // // If this is a parameter of local variable. // public bool IsParameter; VariableInfo[] sub_info; VariableInfo (string name, TypeSpec type, int offset, IMemberContext context) { this.Name = name; this.Offset = offset; this.TypeInfo = TypeInfo.GetTypeInfo (type, context); Length = TypeInfo.TotalLength; Initialize (); } VariableInfo (VariableInfo parent, TypeInfo type) { this.Name = parent.Name; this.TypeInfo = type; this.Offset = parent.Offset + type.Offset; this.Length = type.TotalLength; this.IsParameter = parent.IsParameter; Initialize (); } void Initialize () { TypeInfo[] sub_fields = TypeInfo.SubStructInfo; if (sub_fields != null) { sub_info = new VariableInfo [sub_fields.Length]; for (int i = 0; i < sub_fields.Length; i++) { if (sub_fields [i] != null) sub_info [i] = new VariableInfo (this, sub_fields [i]); } } else sub_info = new VariableInfo [0]; } public static VariableInfo Create (BlockContext bc, LocalVariable variable) { var info = new VariableInfo (variable.Name, variable.Type, bc.AssignmentInfoOffset, bc); bc.AssignmentInfoOffset += info.Length; return info; } public static VariableInfo Create (BlockContext bc, Parameter parameter) { var info = new VariableInfo (parameter.Name, parameter.Type, bc.AssignmentInfoOffset, bc) { IsParameter = true }; bc.AssignmentInfoOffset += info.Length; return info; } public bool IsAssigned (DefiniteAssignmentBitSet vector) { if (vector == null) return true; if (vector [Offset]) return true; // Unless this is a struct if (!TypeInfo.IsStruct) return false; // // Following case cannot be handled fully by SetStructFieldAssigned // because we may encounter following case // // struct A { B b } // struct B { int value; } // // setting a.b.value is propagated only to B's vector and not upwards to possible parents // // // Each field must be assigned // for (int i = Offset + 1; i <= TypeInfo.Length + Offset; i++) { if (!vector[i]) return false; } // Ok, now check all fields which are structs. for (int i = 0; i < sub_info.Length; i++) { VariableInfo sinfo = sub_info[i]; if (sinfo == null) continue; if (!sinfo.IsAssigned (vector)) return false; } vector.Set (Offset); return true; } public bool IsEverAssigned { get; set; } public bool IsFullyInitialized (FlowAnalysisContext fc, Location loc) { return TypeInfo.IsFullyInitialized (fc, this, loc); } public bool IsStructFieldAssigned (DefiniteAssignmentBitSet vector, string field_name) { int field_idx = TypeInfo.GetFieldIndex (field_name); if (field_idx == 0) return true; return vector [Offset + field_idx]; } public void SetAssigned (DefiniteAssignmentBitSet vector, bool generatedAssignment) { if (Length == 1) vector.Set (Offset); else vector.Set (Offset, Length); if (!generatedAssignment) IsEverAssigned = true; } public void SetStructFieldAssigned (DefiniteAssignmentBitSet vector, string field_name) { if (vector [Offset]) return; int field_idx = TypeInfo.GetFieldIndex (field_name); if (field_idx == 0) return; var complex_field = TypeInfo.GetStructField (field_name); if (complex_field != null) { vector.Set (Offset + complex_field.Offset, complex_field.TotalLength); } else { vector.Set (Offset + field_idx); } IsEverAssigned = true; // // Each field must be assigned before setting master bit // for (int i = Offset + 1; i < TypeInfo.TotalLength + Offset; i++) { if (!vector[i]) return; } // // Set master struct flag to assigned when all tested struct // fields have been assigned // vector.Set (Offset); } public VariableInfo GetStructFieldInfo (string fieldName) { TypeInfo type = TypeInfo.GetStructField (fieldName); if (type == null) return null; return new VariableInfo (this, type); } public override string ToString () { return String.Format ("Name={0} Offset={1} Length={2} {3})", Name, Offset, Length, TypeInfo); } } public struct Reachability { readonly bool unreachable; Reachability (bool unreachable) { this.unreachable = unreachable; } public bool IsUnreachable { get { return unreachable; } } public static Reachability CreateUnreachable () { return new Reachability (true); } public static Reachability operator & (Reachability a, Reachability b) { return new Reachability (a.unreachable && b.unreachable); } public static Reachability operator | (Reachability a, Reachability b) { return new Reachability (a.unreachable | b.unreachable); } } // // Special version of bit array. Many operations can be simplified because // we are always dealing with arrays of same sizes // public class DefiniteAssignmentBitSet { const uint copy_on_write_flag = 1u << 31; uint bits; // Used when bits overflows int[] large_bits; public static readonly DefiniteAssignmentBitSet Empty = new DefiniteAssignmentBitSet (0); public DefiniteAssignmentBitSet (int length) { if (length > 31) large_bits = new int[(length + 31) / 32]; } public DefiniteAssignmentBitSet (DefiniteAssignmentBitSet source) { if (source.large_bits != null) { large_bits = source.large_bits; bits = source.bits | copy_on_write_flag; } else { bits = source.bits & ~copy_on_write_flag; } } public static DefiniteAssignmentBitSet operator & (DefiniteAssignmentBitSet a, DefiniteAssignmentBitSet b) { if (IsEqual (a, b)) return a; DefiniteAssignmentBitSet res; if (a.large_bits == null) { res = new DefiniteAssignmentBitSet (a); res.bits &= (b.bits & ~copy_on_write_flag); return res; } res = new DefiniteAssignmentBitSet (a); res.Clone (); var dest = res.large_bits; var src = b.large_bits; for (int i = 0; i < dest.Length; ++i) { dest[i] &= src[i]; } return res; } public static DefiniteAssignmentBitSet operator | (DefiniteAssignmentBitSet a, DefiniteAssignmentBitSet b) { if (IsEqual (a, b)) return a; DefiniteAssignmentBitSet res; if (a.large_bits == null) { res = new DefiniteAssignmentBitSet (a); res.bits |= b.bits; res.bits &= ~copy_on_write_flag; return res; } res = new DefiniteAssignmentBitSet (a); res.Clone (); var dest = res.large_bits; var src = b.large_bits; for (int i = 0; i < dest.Length; ++i) { dest[i] |= src[i]; } return res; } public static DefiniteAssignmentBitSet And (List das) { if (das.Count == 0) throw new ArgumentException ("Empty das"); DefiniteAssignmentBitSet res = das[0]; for (int i = 1; i < das.Count; ++i) { res &= das[i]; } return res; } bool CopyOnWrite { get { return (bits & copy_on_write_flag) != 0; } } int Length { get { return large_bits == null ? 31 : large_bits.Length * 32; } } public void Set (int index) { if (CopyOnWrite && !this[index]) Clone (); SetBit (index); } public void Set (int index, int length) { for (int i = 0; i < length; ++i) { if (CopyOnWrite && !this[index + i]) Clone (); SetBit (index + i); } } public bool this[int index] { get { return GetBit (index); } } public override string ToString () { var length = Length; StringBuilder sb = new StringBuilder (length); for (int i = 0; i < length; ++i) { sb.Append (this[i] ? '1' : '0'); } return sb.ToString (); } void Clone () { large_bits = (int[]) large_bits.Clone (); } bool GetBit (int index) { return large_bits == null ? (bits & (1 << index)) != 0 : (large_bits[index >> 5] & (1 << (index & 31))) != 0; } void SetBit (int index) { if (large_bits == null) bits = (uint) ((int) bits | (1 << index)); else large_bits[index >> 5] |= (1 << (index & 31)); } static bool IsEqual (DefiniteAssignmentBitSet a, DefiniteAssignmentBitSet b) { if (a.large_bits == null) return (a.bits & ~copy_on_write_flag) == (b.bits & ~copy_on_write_flag); for (int i = 0; i < a.large_bits.Length; ++i) { if (a.large_bits[i] != b.large_bits[i]) return false; } return true; } public static bool IsIncluded (DefiniteAssignmentBitSet set, DefiniteAssignmentBitSet test) { var set_bits = set.large_bits; if (set_bits == null) return (set.bits & test.bits & ~copy_on_write_flag) == (set.bits & ~copy_on_write_flag); var test_bits = test.large_bits; for (int i = 0; i < set_bits.Length; ++i) { if ((set_bits[i] & test_bits[i]) != set_bits[i]) return false; } return true; } } }