From 9974734473dffd687f639a2a876b22a22d44e8ec Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 14 Mar 2011 23:42:06 +0100 Subject: [PATCH 01/11] Use a property to return the list of annotations. This makes debugging annotations easier, as properties can be evaluated in the debugger. --- ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs | 56 ++++++-------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs b/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs index dafcdc2533..8264bdc9d2 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs @@ -537,49 +537,23 @@ namespace ICSharpCode.NRefactory.CSharp return null; } - public IEnumerable Annotations() where T: class - { - object annotations = this.annotations; - AnnotationList list = annotations as AnnotationList; - if (list != null) { - List result = new List(); - lock (list) { - foreach (object obj in list) { - T t = obj as T; - if (t != null) - result.Add(t); - } - } - return result; - } else { - T t = annotations as T; - if (t != null) - return new T[] { t }; - else - return Enumerable.Empty(); - } - } - - public IEnumerable Annotations(Type type) - { - if (type == null) - throw new ArgumentNullException("type"); - object annotations = this.annotations; - AnnotationList list = annotations as AnnotationList; - if (list != null) { - List result = new List(); - lock (list) { - foreach (object obj in list) { - if (type.IsInstanceOfType(obj)) - result.Add(obj); + /// + /// Gets all annotations stored on this AstNode. + /// + public IEnumerable Annotations { + get { + object annotations = this.annotations; + AnnotationList list = annotations as AnnotationList; + if (list != null) { + lock (list) { + return list.ToArray(); } + } else { + if (annotations != null) + return new object[] { annotations }; + else + return Enumerable.Empty(); } - return result; - } else { - if (type.IsInstanceOfType(annotations)) - return new object[] { annotations }; - else - return Enumerable.Empty(); } } #endregion From f68a49b509ccfd8f564814911cfb1ccd1eaa4d73 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Tue, 15 Mar 2011 04:01:17 +0100 Subject: [PATCH 02/11] Improve spacing in fixed statement output. --- ICSharpCode.NRefactory/CSharp/Ast/ComposedType.cs | 2 ++ ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/ICSharpCode.NRefactory/CSharp/Ast/ComposedType.cs b/ICSharpCode.NRefactory/CSharp/Ast/ComposedType.cs index ce8ad9879c..b888cdea23 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/ComposedType.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/ComposedType.cs @@ -55,6 +55,8 @@ namespace ICSharpCode.NRefactory.CSharp return GetChildrenByRole(PointerRole).Count(); } set { + if (value < 0) + throw new ArgumentOutOfRangeException(); int d = this.PointerRank; while (d > value) { GetChildByRole(PointerRole).Remove(); diff --git a/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs b/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs index f12d3ffac8..da843f39cc 100644 --- a/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs @@ -1387,9 +1387,13 @@ namespace ICSharpCode.NRefactory.CSharp { StartNode(fixedStatement); WriteKeyword("fixed"); + Space(policy.UsingParentheses); LPar(); + Space(policy.WithinUsingParentheses); fixedStatement.Type.AcceptVisitor(this, data); + Space(); WriteCommaSeparatedList(fixedStatement.Variables); + Space(policy.WithinUsingParentheses); RPar(); WriteEmbeddedStatement(fixedStatement.EmbeddedStatement); return EndNode(fixedStatement); From 3a4fdf2b77020d755e2e3db603e7ad2d83010aee Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 18 Mar 2011 01:31:13 +0100 Subject: [PATCH 03/11] Add support for non-custom attributes to CecilLoader. --- .../TypeSystem/TypeSystemTests.TestCase.cs | 28 ++ .../TypeSystem/TypeSystemTests.cs | 101 +++++++ .../TypeSystem/CecilLoader.cs | 258 ++++++++++++++++-- .../TypeSystem/IAccessor.cs | 5 + .../TypeSystem/IAttribute.cs | 6 + ICSharpCode.NRefactory/TypeSystem/IMethod.cs | 2 +- .../TypeSystem/ITypeDefinition.cs | 6 + .../Implementation/DefaultAccessor.cs | 17 +- .../Implementation/DefaultAttribute.cs | 60 +++- .../Implementation/DefaultTypeDefinition.cs | 9 + 10 files changed, 450 insertions(+), 42 deletions(-) diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.TestCase.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.TestCase.cs index cfce622458..5092401ed3 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.TestCase.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.TestCase.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; [assembly: ICSharpCode.NRefactory.TypeSystem.TestCase.TypeTestAttribute( 42, typeof(System.Action<>), typeof(IDictionary>))] @@ -65,4 +66,31 @@ namespace ICSharpCode.NRefactory.TypeSystem.TestCase { public MyStructWithCtor(int a) {} } + + [Serializable] + public class NonCustomAttributes + { + [NonSerialized] + public readonly int NonSerializedField; + + [DllImport("unmanaged.dll", CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DllMethod([In, Out] ref int p); + } + + [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 8)] + public struct ExplicitFieldLayoutStruct + { + [FieldOffset(0)] + public int Field0; + + [FieldOffset(100)] + public int Field100; + } + + public class ParameterTests + { + public void MethodWithOutParameter(out int x) { x = 0; } + public void MethodWithParamsArray(params object[] x) {} + } } diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs index ba43e776d6..2959f00ed9 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Runtime.InteropServices; using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.NRefactory.TypeSystem.TestCase; using NUnit.Framework; @@ -261,5 +262,105 @@ namespace ICSharpCode.NRefactory.TypeSystem Assert.AreEqual(2, ctors.Count()); Assert.IsFalse(ctors.Any(c => c.IsStatic)); } + + [Test] + public void SerializableAttribute() + { + IAttribute attr = ctx.GetClass(typeof(NonCustomAttributes)).Attributes.Single(); + Assert.AreEqual("System.SerializableAttribute", attr.AttributeType.Resolve(ctx).FullName); + } + + [Test] + public void NonSerializedAttribute() + { + IField field = ctx.GetClass(typeof(NonCustomAttributes)).Fields.Single(f => f.Name == "NonSerializedField"); + Assert.AreEqual("System.NonSerializedAttribute", field.Attributes.Single().AttributeType.Resolve(ctx).FullName); + } + + [Test] + public void ExplicitStructLayoutAttribute() + { + IAttribute attr = ctx.GetClass(typeof(ExplicitFieldLayoutStruct)).Attributes.Single(); + Assert.AreEqual("System.Runtime.InteropServices.StructLayoutAttribute", attr.AttributeType.Resolve(ctx).FullName); + IConstantValue arg1 = attr.PositionalArguments.Single(); + Assert.AreEqual("System.Runtime.InteropServices.LayoutKind", arg1.GetValueType(ctx).FullName); + Assert.AreEqual((int)LayoutKind.Explicit, arg1.GetValue(ctx)); + + var arg2 = attr.NamedArguments[0]; + Assert.AreEqual("CharSet", arg2.Key); + Assert.AreEqual("System.Runtime.InteropServices.CharSet", arg2.Value.GetValueType(ctx).FullName); + Assert.AreEqual((int)CharSet.Unicode, arg2.Value.GetValue(ctx)); + + var arg3 = attr.NamedArguments[1]; + Assert.AreEqual("Pack", arg3.Key); + Assert.AreEqual("System.Int32", arg3.Value.GetValueType(ctx).FullName); + Assert.AreEqual(8, arg3.Value.GetValue(ctx)); + } + + [Test] + public void FieldOffsetAttribute() + { + IField field = ctx.GetClass(typeof(ExplicitFieldLayoutStruct)).Fields.Single(f => f.Name == "Field0"); + Assert.AreEqual("System.Runtime.InteropServices.FieldOffsetAttribute", field.Attributes.Single().AttributeType.Resolve(ctx).FullName); + IConstantValue arg = field.Attributes.Single().PositionalArguments.Single(); + Assert.AreEqual("System.Int32", arg.GetValueType(ctx).FullName); + Assert.AreEqual(0, arg.GetValue(ctx)); + + field = ctx.GetClass(typeof(ExplicitFieldLayoutStruct)).Fields.Single(f => f.Name == "Field100"); + Assert.AreEqual("System.Runtime.InteropServices.FieldOffsetAttribute", field.Attributes.Single().AttributeType.Resolve(ctx).FullName); + arg = field.Attributes.Single().PositionalArguments.Single(); + Assert.AreEqual("System.Int32", arg.GetValueType(ctx).FullName); + Assert.AreEqual(100, arg.GetValue(ctx)); + } + + [Test] + public void DllImportAttribute() + { + IMethod method = ctx.GetClass(typeof(NonCustomAttributes)).Methods.Single(m => m.Name == "DllMethod"); + IAttribute dllImport = method.Attributes.Single(); + Assert.AreEqual("System.Runtime.InteropServices.DllImportAttribute", dllImport.AttributeType.Resolve(ctx).FullName); + Assert.AreEqual("unmanaged.dll", dllImport.PositionalArguments[0].GetValue(ctx)); + Assert.AreEqual((int)CharSet.Unicode, dllImport.NamedArguments.Single().Value.GetValue(ctx)); + } + + [Test] + public void InOutParametersOnRefMethod() + { + IParameter p = ctx.GetClass(typeof(NonCustomAttributes)).Methods.Single(m => m.Name == "DllMethod").Parameters.Single(); + Assert.IsTrue(p.IsRef); + Assert.IsFalse(p.IsOut); + Assert.AreEqual(2, p.Attributes.Count); + Assert.AreEqual("System.Runtime.InteropServices.InAttribute", p.Attributes[0].AttributeType.Resolve(ctx).FullName); + Assert.AreEqual("System.Runtime.InteropServices.OutAttribute", p.Attributes[1].AttributeType.Resolve(ctx).FullName); + } + + [Test] + public void MarshalAsAttributeOnMethod() + { + IMethod method = ctx.GetClass(typeof(NonCustomAttributes)).Methods.Single(m => m.Name == "DllMethod"); + IAttribute marshalAs = method.ReturnTypeAttributes.Single(); + Assert.AreEqual((int)UnmanagedType.Bool, marshalAs.PositionalArguments.Single().GetValue(ctx)); + } + + [Test] + public void MethodWithOutParameter() + { + IParameter p = ctx.GetClass(typeof(ParameterTests)).Methods.Single(m => m.Name == "MethodWithOutParameter").Parameters.Single(); + Assert.IsFalse(p.IsRef); + Assert.IsTrue(p.IsOut); + Assert.AreEqual(0, p.Attributes.Count); + Assert.IsTrue(p.Type is ByReferenceTypeReference); + } + + [Test] + public void MethodWithParamsArray() + { + IParameter p = ctx.GetClass(typeof(ParameterTests)).Methods.Single(m => m.Name == "MethodWithParamsArray").Parameters.Single(); + Assert.IsFalse(p.IsRef); + Assert.IsFalse(p.IsOut); + Assert.IsTrue(p.IsParams); + Assert.AreEqual(0, p.Attributes.Count); + Assert.IsTrue(p.Type is ArrayTypeReference); + } } } diff --git a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs index 7564ed9959..74cbdb91ce 100644 --- a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs +++ b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs @@ -46,6 +46,10 @@ namespace ICSharpCode.NRefactory.TypeSystem #endregion #region Load From AssemblyDefinition + /// + /// Loads the assembly definition into a project content. + /// + /// IProjectContent that represents the assembly public IProjectContent LoadAssembly(AssemblyDefinition assemblyDefinition) { if (assemblyDefinition == null) @@ -144,7 +148,7 @@ namespace ICSharpCode.NRefactory.TypeSystem public void Dispose() { - // Disposibng the synchronization context has no effect + // Disposing the synchronization context has no effect } string IDocumentationProvider.GetDocumentation(IEntity entity) @@ -297,14 +301,13 @@ namespace ICSharpCode.NRefactory.TypeSystem } } - const string DynamicAttributeFullName = "System.Runtime.CompilerServices.DynamicAttribute"; - static bool HasDynamicAttribute(ICustomAttributeProvider attributeProvider, int typeIndex) { if (attributeProvider == null || !attributeProvider.HasCustomAttributes) return false; foreach (CustomAttribute a in attributeProvider.CustomAttributes) { - if (a.Constructor.DeclaringType.FullName == DynamicAttributeFullName) { + TypeReference type = a.AttributeType; + if (type.Name == "DynamicAttribute" && type.Namespace == "System.Runtime.CompilerServices") { if (a.ConstructorArguments.Count == 1) { CustomAttributeArgument[] values = a.ConstructorArguments[0].Value as CustomAttributeArgument[]; if (values != null && typeIndex < values.Length && values[typeIndex].Value is bool) @@ -325,21 +328,150 @@ namespace ICSharpCode.NRefactory.TypeSystem } } + static readonly IAttribute inAttribute = new DefaultAttribute(typeof(InAttribute).ToTypeReference(), null); + static readonly IAttribute outAttribute = new DefaultAttribute(typeof(OutAttribute).ToTypeReference(), null); + void AddAttributes(ParameterDefinition parameter, DefaultParameter targetParameter) { + if (!targetParameter.IsOut) { + if (parameter.IsIn) + targetParameter.Attributes.Add(inAttribute); + if (parameter.IsOut) + targetParameter.Attributes.Add(outAttribute); + } if (parameter.HasCustomAttributes) { AddCustomAttributes(parameter.CustomAttributes, targetParameter.Attributes); } } - void AddAttributes(MethodDefinition accessorMethod, DefaultAccessor targetAccessor) + static readonly ITypeReference dllImportAttributeTypeRef = typeof(DllImportAttribute).ToTypeReference(); + static readonly SimpleConstantValue trueValue = new SimpleConstantValue(KnownTypeReference.Boolean, true); + static readonly SimpleConstantValue falseValue = new SimpleConstantValue(KnownTypeReference.Boolean, true); + static readonly ITypeReference callingConventionTypeRef = typeof(CallingConvention).ToTypeReference(); + static readonly IAttribute preserveSigAttribute = new DefaultAttribute(typeof(PreserveSigAttribute).ToTypeReference(), null); + static readonly ITypeReference methodImplAttributeTypeRef = typeof(MethodImplAttribute).ToTypeReference(); + static readonly ITypeReference methodImplOptionsTypeRef = typeof(MethodImplOptions).ToTypeReference(); + + bool HasAnyAttributes(MethodDefinition methodDefinition) { - if (accessorMethod.HasCustomAttributes) { - AddCustomAttributes(accessorMethod.CustomAttributes, targetAccessor.Attributes); + if (methodDefinition.HasPInvokeInfo) + return true; + if ((methodDefinition.ImplAttributes & ~MethodImplAttributes.CodeTypeMask) != 0) + return true; + if (methodDefinition.MethodReturnType.HasFieldMarshal) + return true; + return methodDefinition.HasCustomAttributes || methodDefinition.MethodReturnType.HasCustomAttributes; + } + + void AddAttributes(MethodDefinition methodDefinition, IList attributes, IList returnTypeAttributes) + { + MethodImplAttributes implAttributes = methodDefinition.ImplAttributes & ~MethodImplAttributes.CodeTypeMask; + + #region DllImportAttribute + if (methodDefinition.HasPInvokeInfo) { + PInvokeInfo info = methodDefinition.PInvokeInfo; + DefaultAttribute dllImport = new DefaultAttribute(dllImportAttributeTypeRef, new[] { KnownTypeReference.String }); + dllImport.PositionalArguments.Add(new SimpleConstantValue(KnownTypeReference.String, info.Module.Name)); + + if (info.IsBestFitDisabled) + AddNamedArgument(dllImport, "BestFitMapping", falseValue); + if (info.IsBestFitEnabled) + AddNamedArgument(dllImport, "BestFitMapping", trueValue); + + CallingConvention callingConvention; + switch (info.Attributes & PInvokeAttributes.CallConvMask) { + case PInvokeAttributes.CallConvCdecl: + callingConvention = CallingConvention.Cdecl; + break; + case PInvokeAttributes.CallConvFastcall: + callingConvention = CallingConvention.FastCall; + break; + case PInvokeAttributes.CallConvStdCall: + callingConvention = CallingConvention.StdCall; + break; + case PInvokeAttributes.CallConvThiscall: + callingConvention = CallingConvention.ThisCall; + break; + case PInvokeAttributes.CallConvWinapi: + callingConvention = CallingConvention.Winapi; + break; + default: + throw new NotSupportedException("unknown calling convention"); + } + if (callingConvention != CallingConvention.Winapi) + AddNamedArgument(dllImport, "CallingConvention", new SimpleConstantValue(callingConventionTypeRef, (int)callingConvention)); + + CharSet charSet = CharSet.None; + switch (info.Attributes & PInvokeAttributes.CharSetMask) { + case PInvokeAttributes.CharSetAnsi: + charSet = CharSet.Ansi; + break; + case PInvokeAttributes.CharSetAuto: + charSet = CharSet.Auto; + break; + case PInvokeAttributes.CharSetUnicode: + charSet = CharSet.Unicode; + break; + } + if (charSet != CharSet.None) + dllImport.NamedArguments.Add(new KeyValuePair( + "CharSet", new SimpleConstantValue(charSetTypeRef, (int)charSet))); + + if (!string.IsNullOrEmpty(info.EntryPoint) && info.EntryPoint != methodDefinition.Name) + AddNamedArgument(dllImport, "EntryPoint", new SimpleConstantValue(KnownTypeReference.String, info.EntryPoint)); + + if (info.IsNoMangle) + AddNamedArgument(dllImport, "ExactSpelling", trueValue); + + if ((implAttributes & MethodImplAttributes.PreserveSig) == MethodImplAttributes.PreserveSig) + implAttributes &= ~MethodImplAttributes.PreserveSig; + else + AddNamedArgument(dllImport, "PreserveSig", falseValue); + + if (info.SupportsLastError) + AddNamedArgument(dllImport, "SetLastError", trueValue); + + if (info.IsThrowOnUnmappableCharDisabled) + AddNamedArgument(dllImport, "ThrowOnUnmappableChar", falseValue); + if (info.IsThrowOnUnmappableCharEnabled) + AddNamedArgument(dllImport, "ThrowOnUnmappableChar", trueValue); + + attributes.Add(dllImport); + } + #endregion + + #region PreserveSigAttribute + if (implAttributes == MethodImplAttributes.PreserveSig) { + attributes.Add(preserveSigAttribute); + implAttributes = 0; + } + #endregion + + #region MethodImplAttribute + if (implAttributes != 0) { + DefaultAttribute methodImpl = new DefaultAttribute(methodImplAttributeTypeRef, new[] { methodImplOptionsTypeRef }); + methodImpl.PositionalArguments.Add(new SimpleConstantValue(methodImplOptionsTypeRef, (int)implAttributes)); + attributes.Add(methodImpl); + } + #endregion + + if (methodDefinition.HasCustomAttributes) { + AddCustomAttributes(methodDefinition.CustomAttributes, attributes); + } + if (methodDefinition.MethodReturnType.HasMarshalInfo) { + returnTypeAttributes.Add(ConvertMarshalInfo(methodDefinition.MethodReturnType.MarshalInfo)); } + if (methodDefinition.MethodReturnType.HasCustomAttributes) { + AddCustomAttributes(methodDefinition.MethodReturnType.CustomAttributes, returnTypeAttributes); + } + } + + static void AddNamedArgument(DefaultAttribute attribute, string name, IConstantValue value) + { + attribute.NamedArguments.Add(new KeyValuePair(name, value)); } - static readonly DefaultAttribute serializableAttribute = new DefaultAttribute(typeof(SerializableAttribute).ToTypeReference()); + static readonly DefaultAttribute serializableAttribute = new DefaultAttribute(typeof(SerializableAttribute).ToTypeReference(), null); static readonly ITypeReference structLayoutAttributeTypeRef = typeof(StructLayoutAttribute).ToTypeReference(); static readonly ITypeReference layoutKindTypeRef = typeof(LayoutKind).ToTypeReference(); static readonly ITypeReference charSetTypeRef = typeof(CharSet).ToTypeReference(); @@ -373,8 +505,9 @@ namespace ICSharpCode.NRefactory.TypeSystem charSet = CharSet.Unicode; break; } - if (layoutKind != LayoutKind.Auto || charSet != CharSet.Ansi || typeDefinition.PackingSize > 0 || typeDefinition.ClassSize > 0) { - DefaultAttribute structLayout = new DefaultAttribute(structLayoutAttributeTypeRef); + LayoutKind defaultLayoutKind = (typeDefinition.IsValueType && !typeDefinition.IsEnum) ? LayoutKind.Sequential: LayoutKind.Auto; + if (layoutKind != defaultLayoutKind || charSet != CharSet.Ansi || typeDefinition.PackingSize > 0 || typeDefinition.ClassSize > 0) { + DefaultAttribute structLayout = new DefaultAttribute(structLayoutAttributeTypeRef, new[] { layoutKindTypeRef }); structLayout.PositionalArguments.Add(new SimpleConstantValue(layoutKindTypeRef, (int)layoutKind)); if (charSet != CharSet.Ansi) { structLayout.NamedArguments.Add(new KeyValuePair( @@ -400,12 +533,58 @@ namespace ICSharpCode.NRefactory.TypeSystem } } + static readonly ITypeReference fieldOffsetAttributeTypeRef = typeof(FieldOffsetAttribute).ToTypeReference(); + static readonly DefaultAttribute nonSerializedAttribute = new DefaultAttribute(typeof(NonSerializedAttribute).ToTypeReference(), null); + + void AddAttributes(FieldDefinition fieldDefinition, IEntity targetEntity) + { + #region FieldOffsetAttribute + if (fieldDefinition.HasLayoutInfo) { + DefaultAttribute fieldOffset = new DefaultAttribute(fieldOffsetAttributeTypeRef, new[] { KnownTypeReference.Int32 }); + fieldOffset.PositionalArguments.Add(new SimpleConstantValue(KnownTypeReference.Int32, fieldDefinition.Offset)); + targetEntity.Attributes.Add(fieldOffset); + } + #endregion + + #region NonSerializedAttribute + if (fieldDefinition.IsNotSerialized) { + targetEntity.Attributes.Add(nonSerializedAttribute); + } + #endregion + + if (fieldDefinition.HasMarshalInfo) { + targetEntity.Attributes.Add(ConvertMarshalInfo(fieldDefinition.MarshalInfo)); + } + + if (fieldDefinition.HasCustomAttributes) { + AddCustomAttributes(fieldDefinition.CustomAttributes, targetEntity.Attributes); + } + } + + #region MarshalAsAttribute (ConvertMarshalInfo) + static readonly ITypeReference marshalAsAttributeTypeRef = typeof(MarshalAsAttribute).ToTypeReference(); + static readonly ITypeReference unmanagedTypeTypeRef = typeof(UnmanagedType).ToTypeReference(); + + static IAttribute ConvertMarshalInfo(MarshalInfo marshalInfo) + { + DefaultAttribute attr = new DefaultAttribute(marshalAsAttributeTypeRef, new[] { unmanagedTypeTypeRef }); + attr.PositionalArguments.Add(new SimpleConstantValue(unmanagedTypeTypeRef, (int)marshalInfo.NativeType)); + // TODO: handle classes derived from MarshalInfo + return attr; + } + #endregion + void AddCustomAttributes(Mono.Collections.Generic.Collection attributes, IList targetCollection) { foreach (var cecilAttribute in attributes) { - if (cecilAttribute.AttributeType.FullName != DynamicAttributeFullName) { - targetCollection.Add(ReadAttribute(cecilAttribute)); + TypeReference type = cecilAttribute.AttributeType; + if (type.Namespace == "System.Runtime.CompilerServices") { + if (type.Name == "DynamicAttribute" || type.Name == "ExtensionAttribute") + continue; + } else if (type.Name == "ParamArrayAttribute" && type.Namespace == "System") { + continue; } + targetCollection.Add(ReadAttribute(cecilAttribute)); } } @@ -413,7 +592,15 @@ namespace ICSharpCode.NRefactory.TypeSystem { if (attribute == null) throw new ArgumentNullException("attribute"); - DefaultAttribute a = new DefaultAttribute(ReadTypeReference(attribute.AttributeType)); + MethodReference ctor = attribute.Constructor; + ITypeReference[] ctorParameters = null; + if (ctor.HasParameters) { + ctorParameters = new ITypeReference[ctor.Parameters.Count]; + for (int i = 0; i < ctorParameters.Length; i++) { + ctorParameters[i] = ReadTypeReference(ctor.Parameters[i].ParameterType); + } + } + DefaultAttribute a = new DefaultAttribute(ReadTypeReference(attribute.AttributeType), ctorParameters); try { if (attribute.HasConstructorArguments) { foreach (var arg in attribute.ConstructorArguments) { @@ -442,8 +629,13 @@ namespace ICSharpCode.NRefactory.TypeSystem #region Read Constant Value public IConstantValue ReadConstantValue(CustomAttributeArgument arg) { - ITypeReference type = ReadTypeReference(arg.Type); object value = arg.Value; + if (value is CustomAttributeArgument) { + // Cecil uses this representation for boxed values + arg = (CustomAttributeArgument)value; + value = arg.Value; + } + ITypeReference type = ReadTypeReference(arg.Type); CustomAttributeArgument[] array = value as CustomAttributeArgument[]; if (array != null) { // TODO: write unit test for this @@ -500,9 +692,9 @@ namespace ICSharpCode.NRefactory.TypeSystem InitNestedTypes(loader); // nested types can be initialized only after generic parameters were created - if (typeDefinition.HasCustomAttributes) { - loader.AddAttributes(typeDefinition, this); - } + loader.AddAttributes(typeDefinition, this); + + this.HasExtensionMethods = HasExtensionAttribute(typeDefinition); // set base classes if (typeDefinition.IsEnum) { @@ -699,7 +891,8 @@ namespace ICSharpCode.NRefactory.TypeSystem else m.ReturnType = ReadTypeReference(method.ReturnType, typeAttributes: method.MethodReturnType, entity: m); - AddAttributes(method, m); + if (HasAnyAttributes(method)) + AddAttributes(method, m.Attributes, m.ReturnTypeAttributes); TranslateModifiers(method, m); if (method.HasParameters) { @@ -708,18 +901,26 @@ namespace ICSharpCode.NRefactory.TypeSystem } } - // mark as extension method is the attribute is set - if (method.IsStatic && method.HasCustomAttributes) { - foreach (var attr in method.CustomAttributes) { - if (attr.AttributeType.FullName == typeof(ExtensionAttribute).FullName) - m.IsExtensionMethod = true; - } + // mark as extension method if the attribute is set + if (method.IsStatic && HasExtensionAttribute(method)) { + m.IsExtensionMethod = true; } FinishReadMember(m); return m; } + static bool HasExtensionAttribute(ICustomAttributeProvider provider) + { + if (provider.HasCustomAttributes) { + foreach (var attr in provider.CustomAttributes) { + if (attr.AttributeType.Name == "ExtensionAttribute" && attr.AttributeType.Namespace == "System.Runtime.CompilerServices") + return true; + } + } + return false; + } + bool IsVisible(MethodAttributes att) { att &= MethodAttributes.MemberAccessMask; @@ -782,14 +983,13 @@ namespace ICSharpCode.NRefactory.TypeSystem var type = ReadTypeReference(parameter.ParameterType, typeAttributes: parameter, entity: parentMember); DefaultParameter p = new DefaultParameter(type, parameter.Name); - AddAttributes(parameter, p); - if (parameter.ParameterType is Mono.Cecil.ByReferenceType) { - if (parameter.IsOut) + if (!parameter.IsIn && parameter.IsOut) p.IsOut = true; else p.IsRef = true; } + AddAttributes(parameter, p); if (parameter.IsOptional) { p.DefaultValue = ReadConstantValue(new CustomAttributeArgument(parameter.ParameterType, parameter.Constant)); @@ -917,10 +1117,10 @@ namespace ICSharpCode.NRefactory.TypeSystem { if (accessorMethod != null && IsVisible(accessorMethod.Attributes)) { Accessibility accessibility = GetAccessibility(accessorMethod.Attributes); - if (accessorMethod.HasCustomAttributes) { + if (HasAnyAttributes(accessorMethod)) { DefaultAccessor a = new DefaultAccessor(); a.Accessibility = accessibility; - AddAttributes(accessorMethod, a); + AddAttributes(accessorMethod, a.Attributes, a.ReturnTypeAttributes); return a; } else { return DefaultAccessor.GetFromAccessibility(accessibility); diff --git a/ICSharpCode.NRefactory/TypeSystem/IAccessor.cs b/ICSharpCode.NRefactory/TypeSystem/IAccessor.cs index ed9278b255..9c33b586ec 100644 --- a/ICSharpCode.NRefactory/TypeSystem/IAccessor.cs +++ b/ICSharpCode.NRefactory/TypeSystem/IAccessor.cs @@ -21,6 +21,11 @@ namespace ICSharpCode.NRefactory.TypeSystem /// IList Attributes { get; } + /// + /// Gets the attributes defined on the return type of the accessor. (e.g. [return: MarshalAs(...)]) + /// + IList ReturnTypeAttributes { get; } + /// /// Gets the accessibility of this accessor. /// diff --git a/ICSharpCode.NRefactory/TypeSystem/IAttribute.cs b/ICSharpCode.NRefactory/TypeSystem/IAttribute.cs index b9bb2dd97d..42f913f45c 100644 --- a/ICSharpCode.NRefactory/TypeSystem/IAttribute.cs +++ b/ICSharpCode.NRefactory/TypeSystem/IAttribute.cs @@ -35,6 +35,12 @@ namespace ICSharpCode.NRefactory.TypeSystem /// Gets the named arguments passed to the attribute. /// IList> NamedArguments { get; } + + /// + /// Resolves the constructor method used for this attribute invocation. + /// Returns null if the constructor cannot be found. + /// + IMethod ResolveConstructor(ITypeResolveContext context); } #if WITH_CONTRACTS diff --git a/ICSharpCode.NRefactory/TypeSystem/IMethod.cs b/ICSharpCode.NRefactory/TypeSystem/IMethod.cs index e66ab07820..7f6241b3dd 100644 --- a/ICSharpCode.NRefactory/TypeSystem/IMethod.cs +++ b/ICSharpCode.NRefactory/TypeSystem/IMethod.cs @@ -16,7 +16,7 @@ namespace ICSharpCode.NRefactory.TypeSystem public interface IMethod : IParameterizedMember { /// - /// Gets the attributes associated with the return type. + /// Gets the attributes associated with the return type. (e.g. [return: MarshalAs(...)]) /// IList ReturnTypeAttributes { get; } diff --git a/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs b/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs index 905a85fdca..ef6568eda1 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs @@ -44,6 +44,12 @@ namespace ICSharpCode.NRefactory.TypeSystem /// Gets all members declared in this class. This is the union of Fields,Properties,Methods and Events. /// IEnumerable Members { get; } + + /// + /// Gets whether this type contains extension methods. + /// + /// This property is used to speed up the search for extension methods. + bool HasExtensionMethods { get; } } #if WITH_CONTRACTS diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAccessor.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAccessor.cs index 9a8580f0a8..917b1f4732 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAccessor.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAccessor.cs @@ -44,6 +44,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation Accessibility accessibility; DomRegion region; IList attributes; + IList returnTypeAttributes; protected override void FreezeInternal() { @@ -75,20 +76,32 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } } + public IList ReturnTypeAttributes { + get { + if (returnTypeAttributes == null) + returnTypeAttributes = new List(); + return returnTypeAttributes; + } + } + void ISupportsInterning.PrepareForInterning(IInterningProvider provider) { attributes = provider.InternList(attributes); + returnTypeAttributes = provider.InternList(returnTypeAttributes); } int ISupportsInterning.GetHashCodeForInterning() { - return (attributes != null ? attributes.GetHashCode() : 0) ^ region.GetHashCode() ^ (int)accessibility; + return (attributes != null ? attributes.GetHashCode() : 0) + ^ (returnTypeAttributes != null ? returnTypeAttributes.GetHashCode() : 0) + ^ region.GetHashCode() ^ (int)accessibility; } bool ISupportsInterning.EqualsForInterning(ISupportsInterning other) { DefaultAccessor a = other as DefaultAccessor; - return a != null && (attributes == a.attributes && accessibility == a.accessibility && region == a.region); + return a != null && (attributes == a.attributes && returnTypeAttributes == a.returnTypeAttributes + && accessibility == a.accessibility && region == a.region); } } } diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAttribute.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAttribute.cs index cb085ca18e..8607ffca81 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAttribute.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAttribute.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Text; @@ -13,8 +14,9 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// public sealed class DefaultAttribute : AbstractFreezable, IAttribute, ISupportsInterning { - DomRegion region; ITypeReference attributeType; + readonly ITypeReference[] constructorParameterTypes; + DomRegion region; IList positionalArguments; IList> namedArguments; @@ -34,11 +36,20 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation base.FreezeInternal(); } - public DefaultAttribute(ITypeReference attributeType) + public DefaultAttribute(ITypeReference attributeType, IEnumerable constructorParameterTypes) { if (attributeType == null) throw new ArgumentNullException("attributeType"); this.attributeType = attributeType; + this.constructorParameterTypes = constructorParameterTypes != null ? constructorParameterTypes.ToArray() : null; + } + + public ITypeReference AttributeType { + get { return attributeType; } + } + + public ReadOnlyCollection ConstructorParameterTypes { + get { return Array.AsReadOnly(constructorParameterTypes); } } public DomRegion Region { @@ -49,14 +60,6 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } } - public ITypeReference AttributeType { - get { return attributeType; } - set { - CheckBeforeMutation(); - attributeType = value; - } - } - public IList PositionalArguments { get { if (positionalArguments == null) @@ -73,6 +76,38 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } } + public IMethod ResolveConstructor(ITypeResolveContext context) + { + IType[] parameterTypes = null; + if (constructorParameterTypes != null && constructorParameterTypes.Length > 0) { + parameterTypes = new IType[constructorParameterTypes.Length]; + for (int i = 0; i < parameterTypes.Length; i++) { + parameterTypes[i] = constructorParameterTypes[i].Resolve(context); + } + } + IMethod bestMatch = null; + foreach (IMethod ctor in attributeType.Resolve(context).GetConstructors(context)) { + if (ctor.IsStatic) + continue; + if (parameterTypes == null) { + if (ctor.Parameters.Count == 0) + return ctor; + } else if (ctor.Parameters.Count == parameterTypes.Length) { + bestMatch = ctor; + bool ok = true; + for (int i = 0; i < parameterTypes.Length; i++) { + if (ctor.Parameters[i].Type != parameterTypes[i]) { + ok = false; + break; + } + } + if (ok) + return ctor; + } + } + return bestMatch; + } + public override string ToString() { StringBuilder b = new StringBuilder(); @@ -100,6 +135,11 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation void ISupportsInterning.PrepareForInterning(IInterningProvider provider) { attributeType = provider.Intern(attributeType); + if (constructorParameterTypes != null) { + for (int i = 0; i < constructorParameterTypes.Length; i++) { + constructorParameterTypes[i] = provider.Intern(constructorParameterTypes[i]); + } + } positionalArguments = provider.InternList(positionalArguments); } diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs index 881d38585b..c707780c42 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs @@ -40,6 +40,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation const ushort FlagShadowing = 0x0004; const ushort FlagSynthetic = 0x0008; const ushort FlagAddDefaultConstructorIfRequired = 0x0010; + const ushort FlagHasExtensionMethods = 0x0020; protected override void FreezeInternal() { @@ -312,6 +313,14 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } } + public bool HasExtensionMethods { + get { return flags[FlagHasExtensionMethods]; } + set { + CheckBeforeMutation(); + flags[FlagHasExtensionMethods] = value; + } + } + public IProjectContent ProjectContent { get { return projectContent; } } From ccfd4ea12c9144b129dae1ade4601b5f7319a018 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 18 Mar 2011 16:12:22 +0100 Subject: [PATCH 04/11] Evaluate constant expressions in definite assignment analysis. --- .../Analysis/DefiniteAssignmentTests.cs | 26 +++++++++++++++-- .../CSharp/Analysis/ControlFlow.cs | 29 ++++++++++++++++--- .../Analysis/DefiniteAssignmentAnalysis.cs | 25 ++++++++++++---- ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs | 1 + .../CSharp/Ast/AstNodeCollection.cs | 1 + .../CSharp/Parser/CSharpParser.cs | 4 +-- .../CSharp/Resolver/CSharpResolver.cs | 2 -- .../Resolver/IResolveVisitorNavigator.cs | 12 ++++++++ .../CSharp/Resolver/ResolveVisitor.cs | 15 ++++++++++ 9 files changed, 99 insertions(+), 16 deletions(-) diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs index 247c1fb394..2e94a1c589 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using ICSharpCode.NRefactory.TypeSystem; using NUnit.Framework; namespace ICSharpCode.NRefactory.CSharp.Analysis @@ -39,7 +40,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis Statement stmt5 = tryCatchStatement.FinallyBlock.Statements.Single(); LabelStatement label = (LabelStatement)block.Statements.ElementAt(1); - DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(block); + DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(block, CecilLoaderTests.Mscorlib); da.Analyze("i"); Assert.AreEqual(0, da.UnassignedVariableUses.Count); Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(tryCatchStatement)); @@ -89,7 +90,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis TrueStatement = new BlockStatement(), FalseStatement = new BlockStatement() }; - DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(ifStmt); + DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(ifStmt, CecilLoaderTests.Mscorlib); da.Analyze("i"); Assert.AreEqual(0, da.UnassignedVariableUses.Count); Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(ifStmt)); @@ -120,7 +121,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis TrueStatement = new BlockStatement(), FalseStatement = new BlockStatement() }; - DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(ifStmt); + DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(ifStmt, CecilLoaderTests.Mscorlib); da.Analyze("i"); Assert.AreEqual(0, da.UnassignedVariableUses.Count); Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(ifStmt)); @@ -128,5 +129,24 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(ifStmt.FalseStatement)); Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(ifStmt)); } + + [Test] + public void WhileTrue() + { + WhileStatement loop = new WhileStatement { + Condition = new PrimitiveExpression(true), + EmbeddedStatement = new BlockStatement { + new AssignmentExpression(new IdentifierExpression("i"), new PrimitiveExpression(0)), + new BreakStatement() + } + }; + DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(loop, CecilLoaderTests.Mscorlib); + da.Analyze("i"); + Assert.AreEqual(0, da.UnassignedVariableUses.Count); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop)); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop.EmbeddedStatement)); + Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusAfter(loop.EmbeddedStatement)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop)); + } } } diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs index 2f1e2b47e3..9a2fa141d5 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs @@ -5,7 +5,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; + using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.TypeSystem; namespace ICSharpCode.NRefactory.CSharp.Analysis { @@ -135,12 +138,24 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } Statement rootStatement; + ResolveVisitor resolveVisitor; List nodes; Dictionary labels; List gotoStatements; - public IList BuildControlFlowGraph(Statement statement) + public IList BuildControlFlowGraph(Statement statement, ITypeResolveContext context, CancellationToken cancellationToken = default(CancellationToken)) { + return BuildControlFlowGraph(statement, new ResolveVisitor( + new CSharpResolver(context, cancellationToken), null, ConstantModeResolveVisitorNavigator.Skip)); + } + + public IList BuildControlFlowGraph(Statement statement, ResolveVisitor resolveVisitor) + { + if (statement == null) + throw new ArgumentNullException("statement"); + if (resolveVisitor == null) + throw new ArgumentNullException("resolveVisitor"); + NodeCreationVisitor nodeCreationVisitor = new NodeCreationVisitor(); nodeCreationVisitor.builder = this; try { @@ -148,6 +163,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis this.labels = new Dictionary(); this.gotoStatements = new List(); this.rootStatement = statement; + this.resolveVisitor = resolveVisitor; ControlFlowNode entryPoint = CreateStartNode(statement); statement.AcceptVisitor(nodeCreationVisitor, entryPoint); @@ -167,6 +183,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis this.labels = null; this.gotoStatements = null; this.rootStatement = null; + this.resolveVisitor = null; } } @@ -206,7 +223,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis ControlFlowNode CreateSpecialNode(Statement statement, ControlFlowNodeType type) { - ControlFlowNode node = CreateNode(statement, null, type); + ControlFlowNode node = CreateNode(null, statement, type); nodes.Add(node); return node; } @@ -238,7 +255,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// The constant value of the expression; or null if the expression is not a constant. ConstantResolveResult EvaluateConstant(Expression expr) { - return null; // TODO: implement this using the C# resolver + return resolveVisitor.Resolve(expr) as ConstantResolveResult; } /// @@ -256,7 +273,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis bool AreEqualConstants(ConstantResolveResult c1, ConstantResolveResult c2) { - return false; // TODO: implement this using the resolver's operator== + if (c1 == null || c2 == null) + return false; + CSharpResolver r = new CSharpResolver(resolveVisitor.TypeResolveContext, resolveVisitor.CancellationToken); + ResolveResult c = r.ResolveBinaryOperator(BinaryOperatorType.Equality, c1, c2); + return c.IsCompileTimeConstant && (c.ConstantValue as bool?) == true; } #endregion diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs index 534e423258..e377022398 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.Utils; namespace ICSharpCode.NRefactory.CSharp.Analysis @@ -46,6 +48,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis readonly List allNodes = new List(); readonly Dictionary beginNodeDict = new Dictionary(); readonly Dictionary endNodeDict = new Dictionary(); + readonly ResolveVisitor resolveVisitor; + readonly CancellationToken cancellationToken; Dictionary nodeStatus = new Dictionary(); Dictionary edgeStatus = new Dictionary(); @@ -54,19 +58,30 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis Queue nodesWithModifiedInput = new Queue(); - public DefiniteAssignmentAnalysis(Statement rootStatement) + public DefiniteAssignmentAnalysis(Statement rootStatement, ITypeResolveContext context, CancellationToken cancellationToken = default(CancellationToken)) + : this(rootStatement, new ResolveVisitor(new CSharpResolver(context, cancellationToken), null, ConstantModeResolveVisitorNavigator.Skip)) { + } + + public DefiniteAssignmentAnalysis(Statement rootStatement, ResolveVisitor resolveVisitor) + { + if (rootStatement == null) + throw new ArgumentNullException("rootStatement"); + if (resolveVisitor == null) + throw new ArgumentNullException("resolveVisitor"); + this.resolveVisitor = resolveVisitor; + this.cancellationToken = resolveVisitor.CancellationToken; visitor.analysis = this; ControlFlowGraphBuilder b = new ControlFlowGraphBuilder(); - allNodes.AddRange(b.BuildControlFlowGraph(rootStatement)); + allNodes.AddRange(b.BuildControlFlowGraph(rootStatement, resolveVisitor)); foreach (AstNode descendant in rootStatement.Descendants) { // Anonymous methods have separate control flow graphs, but we also need to analyze those. AnonymousMethodExpression ame = descendant as AnonymousMethodExpression; if (ame != null) - allNodes.AddRange(b.BuildControlFlowGraph(ame.Body)); + allNodes.AddRange(b.BuildControlFlowGraph(ame.Body, resolveVisitor)); LambdaExpression lambda = descendant as LambdaExpression; if (lambda != null && lambda.Body is Statement) - allNodes.AddRange(b.BuildControlFlowGraph((Statement)lambda.Body)); + allNodes.AddRange(b.BuildControlFlowGraph((Statement)lambda.Body, resolveVisitor)); } // Verify that we created nodes for all statements: Debug.Assert(!rootStatement.DescendantsAndSelf.OfType().Except(allNodes.Select(n => n.NextStatement)).Any()); @@ -289,7 +304,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// The constant value of the expression; or null if the expression is not a constant. ConstantResolveResult EvaluateConstant(Expression expr) { - return null; // TODO: implement this using the C# resolver + return resolveVisitor.Resolve(expr) as ConstantResolveResult; } /// diff --git a/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs b/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs index 8264bdc9d2..bd5fbcc586 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs @@ -129,6 +129,7 @@ namespace ICSharpCode.NRefactory.CSharp get { AstNode next; for (AstNode cur = firstChild; cur != null; cur = next) { + Debug.Assert(cur.parent == this); // Remember next before yielding cur. // This allows removing/replacing nodes while iterating through the list. next = cur.nextSibling; diff --git a/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs b/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs index 07e0626721..a8c11cb993 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs @@ -117,6 +117,7 @@ namespace ICSharpCode.NRefactory.CSharp { AstNode next; for (AstNode cur = node.FirstChild; cur != null; cur = next) { + Debug.Assert(cur.Parent == node); // Remember next before yielding cur. // This allows removing/replacing nodes while iterating through the list. next = cur.NextSibling; diff --git a/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs b/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs index 68ac7ee2f6..a056c8ba16 100644 --- a/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs +++ b/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs @@ -374,9 +374,9 @@ namespace ICSharpCode.NRefactory.CSharp variable.AddChild (new Identifier (em.Name, Convert (em.Location)), AstNode.Roles.Identifier); if (em.Initializer != null) { - var initializer = (VariableInitializer)em.Initializer.Accept (this); + var initializer = (Expression)em.Initializer.Accept (this); if (initializer != null) - variable.AddChild (initializer, AstNode.Roles.Variable); + variable.AddChild (initializer, EnumMemberDeclaration.InitializerRole); } newField.AddChild (variable, AstNode.Roles.Variable); diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs index 904bbae995..fd43e18c4e 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs @@ -33,7 +33,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver this.context = context; } - #if !DOTNET35 public CSharpResolver(ITypeResolveContext context, CancellationToken cancellationToken) { if (context == null) @@ -41,7 +40,6 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver this.context = context; this.cancellationToken = cancellationToken; } - #endif #endregion #region Properties diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/IResolveVisitorNavigator.cs b/ICSharpCode.NRefactory/CSharp/Resolver/IResolveVisitorNavigator.cs index 0ae7b02a28..be956b22fa 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/IResolveVisitorNavigator.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/IResolveVisitorNavigator.cs @@ -37,4 +37,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// ResolveAll } + + sealed class ConstantModeResolveVisitorNavigator : IResolveVisitorNavigator + { + ResolveVisitorNavigationMode mode; + + public static readonly IResolveVisitorNavigator Skip = new ConstantModeResolveVisitorNavigator { mode = ResolveVisitorNavigationMode.Skip }; + + ResolveVisitorNavigationMode IResolveVisitorNavigator.Scan(AstNode node) + { + return mode; + } + } } diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs b/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs index 060bdfa7fa..2c4002a416 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs @@ -74,6 +74,20 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } #endregion + /// + /// Gets the TypeResolveContext used by this ResolveVisitor. + /// + public ITypeResolveContext TypeResolveContext { + get { return resolver.Context; } + } + + /// + /// Gets the CancellationToken used by this ResolveVisitor. + /// + public CancellationToken CancellationToken { + get { return resolver.cancellationToken; } + } + #region Scan / Resolve bool resolverEnabled { get { return mode != ResolveVisitorNavigationMode.Scan; } @@ -118,6 +132,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver mode = ResolveVisitorNavigationMode.Resolve; ResolveResult result; if (!cache.TryGetValue(node, out result)) { + resolver.cancellationToken.ThrowIfCancellationRequested(); result = cache[node] = node.AcceptVisitor(this, null) ?? errorResult; } if (wasScan) From 5660677750ae5e11e2b3b3ae642b283dc74b5326 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 19 Mar 2011 22:07:07 +0100 Subject: [PATCH 05/11] Add InsertAfter/InsertBefore to AstNodeCollection. --- ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs | 10 ++++++++++ ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs | 2 +- .../TypeSystem/ITypeResolveContext.cs | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs b/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs index a8c11cb993..98beaeaefb 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs @@ -193,5 +193,15 @@ namespace ICSharpCode.NRefactory.CSharp } return false; } + + public void InsertAfter(T existingItem, T newItem) + { + node.InsertChildAfter(existingItem, newItem, role); + } + + public void InsertBefore(T existingItem, T newItem) + { + node.InsertChildBefore(existingItem, newItem, role); + } } } diff --git a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs index 74cbdb91ce..26d79e4a9b 100644 --- a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs +++ b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs @@ -35,7 +35,7 @@ namespace ICSharpCode.NRefactory.TypeSystem public bool IncludeInternalMembers { get; set; } /// - /// Gets/Sets the documentation provider that is used to retrive the XML documentation for all members. + /// Gets/Sets the documentation provider that is used to retrieve the XML documentation for all members. /// public IDocumentationProvider DocumentationProvider { get; set; } diff --git a/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs b/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs index 3c89b13ade..9cdfb4133d 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs @@ -44,7 +44,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// Language-specific rules for how namespace names are compared /// List of classes within that namespace. /// - /// If this method is called within using (pc.Synchronize()), then the returned enumerable is valid + /// If this method is called within using (var spc = pc.Synchronize()), then the returned enumerable is valid /// only until the end of the synchronize block. /// IEnumerable GetClasses(string nameSpace, StringComparer nameComparer); @@ -53,7 +53,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// Retrieves all namespaces. /// /// - /// If this method is called within using (pc.Synchronize()), then the returned enumerable is valid + /// If this method is called within using (var spc = pc.Synchronize()), then the returned enumerable is valid /// only until the end of the synchronize block. /// IEnumerable GetNamespaces(); From 3cc6fabec45c5bf6207b833a1ea74eac4b460c02 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 20 Mar 2011 00:54:57 +0100 Subject: [PATCH 06/11] Allow performing definite assignment analysis without providing an ITypeResolveContext. --- .../Analysis/DefiniteAssignmentTests.cs | 49 ++++++++ .../CSharp/Analysis/ControlFlow.cs | 25 ++-- .../Analysis/DefiniteAssignmentAnalysis.cs | 35 ++++-- .../CSharp/Analysis/MinimalResolveContext.cs | 116 ++++++++++++++++++ .../CSharp/OutputVisitor/OutputVisitor.cs | 2 +- .../ICSharpCode.NRefactory.csproj | 1 + 6 files changed, 209 insertions(+), 19 deletions(-) create mode 100644 ICSharpCode.NRefactory/CSharp/Analysis/MinimalResolveContext.cs diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs index 2e94a1c589..10373b84ca 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs @@ -148,5 +148,54 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusAfter(loop.EmbeddedStatement)); Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop)); } + + [Test] + public void ForLoop() + { + ForStatement loop = new ForStatement { + Initializers = { + new ExpressionStatement( + new AssignmentExpression(new IdentifierExpression("i"), new PrimitiveExpression(0)) + ) + }, + Condition = new BinaryOperatorExpression(new IdentifierExpression("i"), BinaryOperatorType.LessThan, new PrimitiveExpression(1000)), + Iterators = { + new ExpressionStatement( + new AssignmentExpression { + Left = new IdentifierExpression("i"), + Operator = AssignmentOperatorType.Add, + Right = new IdentifierExpression("j") + } + ) + }, + EmbeddedStatement = new ExpressionStatement( + new AssignmentExpression(new IdentifierExpression("j"), new IdentifierExpression("i")) + )}; + + DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(loop, CecilLoaderTests.Mscorlib); + da.Analyze("i"); + Assert.AreEqual(0, da.UnassignedVariableUses.Count); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop)); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop.Initializers.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop.Initializers.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBeforeLoopCondition(loop)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(loop.EmbeddedStatement)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop.EmbeddedStatement)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(loop.Iterators.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop.Iterators.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop)); + + da.Analyze("j"); + Assert.AreEqual(0, da.UnassignedVariableUses.Count); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop)); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop.Initializers.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(loop.Initializers.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBeforeLoopCondition(loop)); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop.EmbeddedStatement)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop.EmbeddedStatement)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(loop.Iterators.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop.Iterators.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(loop)); + } } } diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs index 9a2fa141d5..92ffa63fa7 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs @@ -146,7 +146,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public IList BuildControlFlowGraph(Statement statement, ITypeResolveContext context, CancellationToken cancellationToken = default(CancellationToken)) { return BuildControlFlowGraph(statement, new ResolveVisitor( - new CSharpResolver(context, cancellationToken), null, ConstantModeResolveVisitorNavigator.Skip)); + new CSharpResolver(context, cancellationToken), + null, ConstantModeResolveVisitorNavigator.Skip)); } public IList BuildControlFlowGraph(Statement statement, ResolveVisitor resolveVisitor) @@ -249,12 +250,21 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis #endregion #region Constant evaluation + /// + /// Gets/Sets whether to handle only primitive expressions as constants (no complex expressions like "a + b"). + /// + public bool EvaluateOnlyPrimitiveConstants { get; set; } + /// /// Evaluates an expression. /// /// The constant value of the expression; or null if the expression is not a constant. - ConstantResolveResult EvaluateConstant(Expression expr) + internal ConstantResolveResult EvaluateConstant(Expression expr) { + if (EvaluateOnlyPrimitiveConstants) { + if (!(expr is PrimitiveExpression || expr is NullReferenceExpression)) + return null; + } return resolveVisitor.Resolve(expr) as ConstantResolveResult; } @@ -262,11 +272,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// Evaluates an expression. /// /// The value of the constant boolean expression; or null if the value is not a constant boolean expression. - bool? EvaluateCondition(Expression expr) + internal bool? EvaluateCondition(Expression expr) { - ConstantResolveResult crr = EvaluateConstant(expr); - if (crr != null) - return crr.ConstantValue as bool?; + ConstantResolveResult rr = EvaluateConstant(expr); + if (rr != null) + return rr.ConstantValue as bool?; else return null; } @@ -328,7 +338,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis foreach (Statement stmt in statements) { if (childNode == null) { childNode = builder.CreateStartNode(stmt); - Connect(source, childNode); + if (source != null) + Connect(source, childNode); } Debug.Assert(childNode.NextStatement == stmt); childNode = stmt.AcceptVisitor(this, childNode); diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs index e377022398..c02b5ea30f 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs @@ -44,10 +44,12 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// public class DefiniteAssignmentAnalysis { + readonly ControlFlowGraphBuilder cfgBuilder = new ControlFlowGraphBuilder(); readonly DefiniteAssignmentVisitor visitor = new DefiniteAssignmentVisitor(); readonly List allNodes = new List(); readonly Dictionary beginNodeDict = new Dictionary(); readonly Dictionary endNodeDict = new Dictionary(); + readonly Dictionary conditionNodeDict = new Dictionary(); readonly ResolveVisitor resolveVisitor; readonly CancellationToken cancellationToken; Dictionary nodeStatus = new Dictionary(); @@ -58,8 +60,14 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis Queue nodesWithModifiedInput = new Queue(); + public DefiniteAssignmentAnalysis(Statement rootStatement, CancellationToken cancellationToken = default(CancellationToken)) + : this(rootStatement, null, cancellationToken) + { + } + public DefiniteAssignmentAnalysis(Statement rootStatement, ITypeResolveContext context, CancellationToken cancellationToken = default(CancellationToken)) - : this(rootStatement, new ResolveVisitor(new CSharpResolver(context, cancellationToken), null, ConstantModeResolveVisitorNavigator.Skip)) + : this(rootStatement, new ResolveVisitor(new CSharpResolver(context ?? MinimalResolveContext.Instance, cancellationToken), + null, ConstantModeResolveVisitorNavigator.Skip)) { } @@ -72,16 +80,18 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis this.resolveVisitor = resolveVisitor; this.cancellationToken = resolveVisitor.CancellationToken; visitor.analysis = this; - ControlFlowGraphBuilder b = new ControlFlowGraphBuilder(); - allNodes.AddRange(b.BuildControlFlowGraph(rootStatement, resolveVisitor)); + if (resolveVisitor.TypeResolveContext is MinimalResolveContext) { + cfgBuilder.EvaluateOnlyPrimitiveConstants = true; + } + allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(rootStatement, resolveVisitor)); foreach (AstNode descendant in rootStatement.Descendants) { // Anonymous methods have separate control flow graphs, but we also need to analyze those. AnonymousMethodExpression ame = descendant as AnonymousMethodExpression; if (ame != null) - allNodes.AddRange(b.BuildControlFlowGraph(ame.Body, resolveVisitor)); + allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(ame.Body, resolveVisitor)); LambdaExpression lambda = descendant as LambdaExpression; if (lambda != null && lambda.Body is Statement) - allNodes.AddRange(b.BuildControlFlowGraph((Statement)lambda.Body, resolveVisitor)); + allNodes.AddRange(cfgBuilder.BuildControlFlowGraph((Statement)lambda.Body, resolveVisitor)); } // Verify that we created nodes for all statements: Debug.Assert(!rootStatement.DescendantsAndSelf.OfType().Except(allNodes.Select(n => n.NextStatement)).Any()); @@ -91,6 +101,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis beginNodeDict.Add(node.NextStatement, node); if (node.Type == ControlFlowNodeType.BetweenStatements || node.Type == ControlFlowNodeType.EndNode) endNodeDict.Add(node.PreviousStatement, node); + if (node.Type == ControlFlowNodeType.LoopCondition) + conditionNodeDict.Add(node.NextStatement, node); } } @@ -136,6 +148,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis return nodeStatus[endNodeDict[statement]]; } + public DefiniteAssignmentStatus GetStatusBeforeLoopCondition(Statement statement) + { + return nodeStatus[conditionNodeDict[statement]]; + } + /// /// Exports the CFG. This method is intended to help debugging issues related to definite assignment. /// @@ -304,7 +321,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// The constant value of the expression; or null if the expression is not a constant. ConstantResolveResult EvaluateConstant(Expression expr) { - return resolveVisitor.Resolve(expr) as ConstantResolveResult; + return cfgBuilder.EvaluateConstant(expr); } /// @@ -313,11 +330,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// The value of the constant boolean expression; or null if the value is not a constant boolean expression. bool? EvaluateCondition(Expression expr) { - ConstantResolveResult crr = EvaluateConstant(expr); - if (crr != null) - return crr.ConstantValue as bool?; - else - return null; + return cfgBuilder.EvaluateCondition(expr); } static DefiniteAssignmentStatus CleanSpecialValues(DefiniteAssignmentStatus status) diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/MinimalResolveContext.cs b/ICSharpCode.NRefactory/CSharp/Analysis/MinimalResolveContext.cs new file mode 100644 index 0000000000..d9909d0d4d --- /dev/null +++ b/ICSharpCode.NRefactory/CSharp/Analysis/MinimalResolveContext.cs @@ -0,0 +1,116 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; + +namespace ICSharpCode.NRefactory.CSharp.Analysis +{ + /// + /// Resolve context represents the minimal mscorlib required for evaluating constants. + /// + sealed class MinimalResolveContext : IProjectContent, ISynchronizedTypeResolveContext + { + static readonly Lazy instance = new Lazy(() => new MinimalResolveContext()); + + public static MinimalResolveContext Instance { + get { return instance.Value; } + } + + readonly ReadOnlyCollection namespaces = Array.AsReadOnly(new string[] { "System" }); + readonly IAttribute[] assemblyAttributes = new IAttribute[0]; + readonly ITypeDefinition systemObject, systemValueType; + readonly ReadOnlyCollection types; + + private MinimalResolveContext() + { + List types = new List(); + types.Add(systemObject = new DefaultTypeDefinition(this, "System", "Object")); + types.Add(systemValueType = new DefaultTypeDefinition(this, "System", "ValueType") { BaseTypes = { systemObject } }); + types.Add(CreateStruct("System", "Boolean")); + types.Add(CreateStruct("System", "SByte")); + types.Add(CreateStruct("System", "Byte")); + types.Add(CreateStruct("System", "Int16")); + types.Add(CreateStruct("System", "UInt16")); + types.Add(CreateStruct("System", "Int32")); + types.Add(CreateStruct("System", "UInt32")); + types.Add(CreateStruct("System", "Int64")); + types.Add(CreateStruct("System", "UInt64")); + types.Add(CreateStruct("System", "Single")); + types.Add(CreateStruct("System", "Double")); + types.Add(CreateStruct("System", "Decimal")); + types.Add(new DefaultTypeDefinition(this, "System", "String") { BaseTypes = { systemObject } }); + foreach (ITypeDefinition type in types) + type.Freeze(); + this.types = types.AsReadOnly(); + } + + ITypeDefinition CreateStruct(string nameSpace, string name) + { + return new DefaultTypeDefinition(this, nameSpace, name) { + ClassType = ClassType.Struct, + BaseTypes = { systemValueType } + }; + } + + public ITypeDefinition GetClass(string nameSpace, string name, int typeParameterCount, StringComparer nameComparer) + { + foreach (ITypeDefinition type in types) { + if (nameComparer.Equals(type.Name, name) && nameComparer.Equals(type.Namespace, nameSpace) && type.TypeParameterCount == typeParameterCount) + return type; + } + return null; + } + + public IEnumerable GetClasses() + { + return types; + } + + public IEnumerable GetClasses(string nameSpace, StringComparer nameComparer) + { + return types.Where(t => nameComparer.Equals(t.Namespace, nameSpace)); + } + + public IEnumerable GetNamespaces() + { + return namespaces; + } + + public string GetNamespace(string nameSpace, StringComparer nameComparer) + { + foreach (string ns in namespaces) { + if (nameComparer.Equals(ns, nameSpace)) + return ns; + } + return null; + } + + public IList AssemblyAttributes { + get { return assemblyAttributes; } + } + + ICSharpCode.NRefactory.Utils.CacheManager ITypeResolveContext.CacheManager { + get { + // We don't support caching + return null; + } + } + + ISynchronizedTypeResolveContext ITypeResolveContext.Synchronize() + { + // This class is immutable + return this; + } + + void IDisposable.Dispose() + { + // exit from Synchronize() block + } + } +} diff --git a/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs b/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs index da843f39cc..9470286999 100644 --- a/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs @@ -428,7 +428,7 @@ namespace ICSharpCode.NRefactory.CSharp if (block != null) VisitBlockStatement(block, null); else - throw new NotImplementedException(); + embeddedStatement.AcceptVisitor(this, null); } void WriteMethodBody(BlockStatement body) diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index 6a57c5315f..e8fdec0b5e 100644 --- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -63,6 +63,7 @@ + From 6f2e9c28cec58002e73071b4df7b0bb47dd6e0c0 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 20 Mar 2011 01:56:26 +0100 Subject: [PATCH 07/11] Order the control flow nodes lexically, and allow restricting definite assignment analysis to a specific lexical range. --- .../CSharp/Analysis/ControlFlow.cs | 36 +++-- .../Analysis/DefiniteAssignmentAnalysis.cs | 151 +++++++++++++----- 2 files changed, 129 insertions(+), 58 deletions(-) diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs index 92ffa63fa7..1660e99b14 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs @@ -222,14 +222,15 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis return node; } - ControlFlowNode CreateSpecialNode(Statement statement, ControlFlowNodeType type) + ControlFlowNode CreateSpecialNode(Statement statement, ControlFlowNodeType type, bool addToNodeList = true) { ControlFlowNode node = CreateNode(null, statement, type); - nodes.Add(node); + if (addToNodeList) + nodes.Add(node); return node; } - ControlFlowNode CreateEndNode(Statement statement) + ControlFlowNode CreateEndNode(Statement statement, bool addToNodeList = true) { Statement nextStatement; if (statement == rootStatement) { @@ -244,7 +245,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } ControlFlowNodeType type = nextStatement != null ? ControlFlowNodeType.BetweenStatements : ControlFlowNodeType.EndNode; ControlFlowNode node = CreateNode(statement, nextStatement, type); - nodes.Add(node); + if (addToNodeList) + nodes.Add(node); return node; } #endregion @@ -259,7 +261,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// Evaluates an expression. /// /// The constant value of the expression; or null if the expression is not a constant. - internal ConstantResolveResult EvaluateConstant(Expression expr) + ConstantResolveResult EvaluateConstant(Expression expr) { if (EvaluateOnlyPrimitiveConstants) { if (!(expr is PrimitiveExpression || expr is NullReferenceExpression)) @@ -272,7 +274,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// Evaluates an expression. /// /// The value of the constant boolean expression; or null if the value is not a constant boolean expression. - internal bool? EvaluateCondition(Expression expr) + bool? EvaluateCondition(Expression expr) { ConstantResolveResult rr = EvaluateConstant(expr); if (rr != null) @@ -418,7 +420,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis int gotoCaseOrDefaultInOuterScope = gotoCaseOrDefault.Count; - ControlFlowNode end = builder.CreateEndNode(switchStatement); + ControlFlowNode end = builder.CreateEndNode(switchStatement, addToNodeList: false); breakTargets.Push(end); foreach (SwitchSection section in switchStatement.SwitchSections) { if (constant == null || section == sectionMatchedByConstant) { @@ -439,6 +441,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis throw new NotImplementedException(); } + builder.nodes.Add(end); return end; } @@ -457,7 +460,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override ControlFlowNode VisitWhileStatement(WhileStatement whileStatement, ControlFlowNode data) { // while (cond) { embeddedStmt; } - ControlFlowNode end = builder.CreateEndNode(whileStatement); + ControlFlowNode end = builder.CreateEndNode(whileStatement, addToNodeList: false); ControlFlowNode conditionNode = builder.CreateSpecialNode(whileStatement, ControlFlowNodeType.LoopCondition); breakTargets.Push(end); continueTargets.Push(conditionNode); @@ -475,14 +478,15 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis breakTargets.Pop(); continueTargets.Pop(); + builder.nodes.Add(end); return end; } public override ControlFlowNode VisitDoWhileStatement(DoWhileStatement doWhileStatement, ControlFlowNode data) { // do { embeddedStmt; } while(cond); - ControlFlowNode end = builder.CreateEndNode(doWhileStatement); - ControlFlowNode conditionNode = builder.CreateSpecialNode(doWhileStatement, ControlFlowNodeType.LoopCondition); + ControlFlowNode end = builder.CreateEndNode(doWhileStatement, addToNodeList: false); + ControlFlowNode conditionNode = builder.CreateSpecialNode(doWhileStatement, ControlFlowNodeType.LoopCondition, addToNodeList: false); breakTargets.Push(end); continueTargets.Push(conditionNode); @@ -499,6 +503,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis breakTargets.Pop(); continueTargets.Pop(); + builder.nodes.Add(conditionNode); + builder.nodes.Add(end); return end; } @@ -506,7 +512,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis { data = HandleStatementList(forStatement.Initializers, data); // for (initializers ; cond; iterators) { embeddedStmt; } - ControlFlowNode end = builder.CreateEndNode(forStatement); + ControlFlowNode end = builder.CreateEndNode(forStatement, addToNodeList: false); ControlFlowNode conditionNode = builder.CreateSpecialNode(forStatement, ControlFlowNodeType.LoopCondition); Connect(data, conditionNode); @@ -536,6 +542,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis if (cond != true) Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse); + builder.nodes.Add(end); return end; } @@ -552,7 +559,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override ControlFlowNode VisitForeachStatement(ForeachStatement foreachStatement, ControlFlowNode data) { // foreach (...) { embeddedStmt } - ControlFlowNode end = builder.CreateEndNode(foreachStatement); + ControlFlowNode end = builder.CreateEndNode(foreachStatement, addToNodeList: false); ControlFlowNode conditionNode = builder.CreateSpecialNode(foreachStatement, ControlFlowNodeType.LoopCondition); Connect(data, conditionNode); @@ -566,7 +573,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis continueTargets.Pop(); Connect(conditionNode, end); - + builder.nodes.Add(end); return end; } @@ -602,7 +609,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override ControlFlowNode VisitTryCatchStatement(TryCatchStatement tryCatchStatement, ControlFlowNode data) { - ControlFlowNode end = builder.CreateEndNode(tryCatchStatement); + ControlFlowNode end = builder.CreateEndNode(tryCatchStatement, addToNodeList: false); var edge = Connect(HandleEmbeddedStatement(tryCatchStatement.TryBlock, data), end); if (!tryCatchStatement.FinallyBlock.IsNull) edge.AddJumpOutOfTryFinally(tryCatchStatement); @@ -616,6 +623,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis // Consumers of the CFG will have to special-case try-finally. HandleEmbeddedStatement(tryCatchStatement.FinallyBlock, data); } + builder.nodes.Add(end); return end; } diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs index c02b5ea30f..fb723aedce 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs @@ -44,21 +44,40 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// public class DefiniteAssignmentAnalysis { - readonly ControlFlowGraphBuilder cfgBuilder = new ControlFlowGraphBuilder(); + sealed class DefiniteAssignmentNode : ControlFlowNode + { + public int Index; + public DefiniteAssignmentStatus NodeStatus; + + public DefiniteAssignmentNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type) + : base(previousStatement, nextStatement, type) + { + } + } + + sealed class DerivedControlFlowGraphBuilder : ControlFlowGraphBuilder + { + protected override ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type) + { + return new DefiniteAssignmentNode(previousStatement, nextStatement, type); + } + } + + readonly DerivedControlFlowGraphBuilder cfgBuilder = new DerivedControlFlowGraphBuilder(); readonly DefiniteAssignmentVisitor visitor = new DefiniteAssignmentVisitor(); - readonly List allNodes = new List(); - readonly Dictionary beginNodeDict = new Dictionary(); - readonly Dictionary endNodeDict = new Dictionary(); - readonly Dictionary conditionNodeDict = new Dictionary(); + readonly List allNodes = new List(); + readonly Dictionary beginNodeDict = new Dictionary(); + readonly Dictionary endNodeDict = new Dictionary(); + readonly Dictionary conditionNodeDict = new Dictionary(); readonly ResolveVisitor resolveVisitor; readonly CancellationToken cancellationToken; - Dictionary nodeStatus = new Dictionary(); Dictionary edgeStatus = new Dictionary(); string variableName; List unassignedVariableUses = new List(); + int analyzedRangeStart, analyzedRangeEnd; - Queue nodesWithModifiedInput = new Queue(); + Queue nodesWithModifiedInput = new Queue(); public DefiniteAssignmentAnalysis(Statement rootStatement, CancellationToken cancellationToken = default(CancellationToken)) : this(rootStatement, null, cancellationToken) @@ -83,20 +102,18 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis if (resolveVisitor.TypeResolveContext is MinimalResolveContext) { cfgBuilder.EvaluateOnlyPrimitiveConstants = true; } - allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(rootStatement, resolveVisitor)); - foreach (AstNode descendant in rootStatement.Descendants) { - // Anonymous methods have separate control flow graphs, but we also need to analyze those. - AnonymousMethodExpression ame = descendant as AnonymousMethodExpression; - if (ame != null) - allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(ame.Body, resolveVisitor)); - LambdaExpression lambda = descendant as LambdaExpression; - if (lambda != null && lambda.Body is Statement) - allNodes.AddRange(cfgBuilder.BuildControlFlowGraph((Statement)lambda.Body, resolveVisitor)); - } - // Verify that we created nodes for all statements: - Debug.Assert(!rootStatement.DescendantsAndSelf.OfType().Except(allNodes.Select(n => n.NextStatement)).Any()); - // Now register the nodes in the dictionaries: - foreach (ControlFlowNode node in allNodes) { + allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(rootStatement, resolveVisitor).Cast()); + for (int i = 0; i < allNodes.Count; i++) { + DefiniteAssignmentNode node = allNodes[i]; + node.Index = i; // assign numbers to the nodes + if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements) { + // Anonymous methods have separate control flow graphs, but we also need to analyze those. + // Iterate backwards so that anonymous methods are inserted in the correct order + for (AstNode child = node.NextStatement.LastChild; child != null; child = child.PrevSibling) { + InsertAnonymousMethods(i + 1, child); + } + } + // Now register the node in the dictionaries: if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements) beginNodeDict.Add(node.NextStatement, node); if (node.Type == ControlFlowNodeType.BetweenStatements || node.Type == ControlFlowNodeType.EndNode) @@ -104,6 +121,32 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis if (node.Type == ControlFlowNodeType.LoopCondition) conditionNodeDict.Add(node.NextStatement, node); } + // Verify that we created nodes for all statements: + Debug.Assert(!rootStatement.DescendantsAndSelf.OfType().Except(allNodes.Select(n => n.NextStatement)).Any()); + this.analyzedRangeStart = 0; + this.analyzedRangeEnd = allNodes.Count - 1; + } + + void InsertAnonymousMethods(int insertPos, AstNode node) + { + // Ignore any statements, as those have their own ControlFlowNode and get handled separately + if (node is Statement) + return; + AnonymousMethodExpression ame = node as AnonymousMethodExpression; + if (ame != null) { + allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph(ame.Body, resolveVisitor).Cast()); + return; + } + LambdaExpression lambda = node as LambdaExpression; + if (lambda != null && lambda.Body is Statement) { + allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph((Statement)lambda.Body, resolveVisitor).Cast()); + return; + } + // Descend into child expressions + // Iterate backwards so that anonymous methods are inserted in the correct order + for (AstNode child = node.LastChild; child != null; child = child.PrevSibling) { + InsertAnonymousMethods(insertPos, child); + } } /// @@ -115,21 +158,37 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } } + /// + /// Sets the range of statements to be analyzed. + /// This method can be used to restrict the analysis to only a part of the method. + /// Only the control flow paths that are fully contained within the selected part will be analyzed. + /// + /// Both 'start' and 'end' are inclusive. + public void SetAnalyzedRange(Statement start, Statement end) + { + int startIndex = beginNodeDict[start].Index; + int endIndex = endNodeDict[end].Index; + if (startIndex > endIndex) + throw new ArgumentException("The start statement must be lexically preceding the end statement"); + this.analyzedRangeStart = startIndex; + this.analyzedRangeEnd = endIndex; + } + public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.PotentiallyAssigned) { this.variableName = variable; // Reset the status: unassignedVariableUses.Clear(); - foreach (ControlFlowNode node in allNodes) { - nodeStatus[node] = DefiniteAssignmentStatus.CodeUnreachable; + foreach (DefiniteAssignmentNode node in allNodes) { + node.NodeStatus = DefiniteAssignmentStatus.CodeUnreachable; foreach (ControlFlowEdge edge in node.Outgoing) edgeStatus[edge] = DefiniteAssignmentStatus.CodeUnreachable; } - ChangeNodeStatus(allNodes[0], initialStatus); + ChangeNodeStatus(allNodes[analyzedRangeStart], initialStatus); // Iterate as long as the input status of some nodes is changing: while (nodesWithModifiedInput.Count > 0) { - ControlFlowNode node = nodesWithModifiedInput.Dequeue(); + DefiniteAssignmentNode node = nodesWithModifiedInput.Dequeue(); DefiniteAssignmentStatus inputStatus = DefiniteAssignmentStatus.CodeUnreachable; foreach (ControlFlowEdge edge in node.Incoming) { inputStatus = MergeStatus(inputStatus, edgeStatus[edge]); @@ -140,17 +199,17 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public DefiniteAssignmentStatus GetStatusBefore(Statement statement) { - return nodeStatus[beginNodeDict[statement]]; + return beginNodeDict[statement].NodeStatus; } public DefiniteAssignmentStatus GetStatusAfter(Statement statement) { - return nodeStatus[endNodeDict[statement]]; + return endNodeDict[statement].NodeStatus; } public DefiniteAssignmentStatus GetStatusBeforeLoopCondition(Statement statement) { - return nodeStatus[conditionNodeDict[statement]]; + return conditionNodeDict[statement].NodeStatus; } /// @@ -161,7 +220,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis GraphVizGraph g = new GraphVizGraph(); g.Title = "DefiniteAssignment - " + variableName; for (int i = 0; i < allNodes.Count; i++) { - string name = nodeStatus[allNodes[i]].ToString() + Environment.NewLine; + string name = "#" + i + " = " + allNodes[i].NodeStatus.ToString() + Environment.NewLine; switch (allNodes[i].Type) { case ControlFlowNodeType.StartNode: case ControlFlowNodeType.BetweenStatements: @@ -179,7 +238,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } g.AddNode(new GraphVizNode(i) { label = name }); foreach (ControlFlowEdge edge in allNodes[i].Outgoing) { - GraphVizEdge ge = new GraphVizEdge(i, allNodes.IndexOf(edge.To)); + GraphVizEdge ge = new GraphVizEdge(i, ((DefiniteAssignmentNode)edge.To).Index); if (edgeStatus.Count > 0) ge.label = edgeStatus[edge].ToString(); if (edge.IsLeavingTryFinally) @@ -218,11 +277,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis return DefiniteAssignmentStatus.PotentiallyAssigned; } - void ChangeNodeStatus(ControlFlowNode node, DefiniteAssignmentStatus inputStatus) + void ChangeNodeStatus(DefiniteAssignmentNode node, DefiniteAssignmentStatus inputStatus) { - if (nodeStatus[node] == inputStatus) + if (node.NodeStatus == inputStatus) return; - nodeStatus[node] = inputStatus; + node.NodeStatus = inputStatus; DefiniteAssignmentStatus outputStatus; switch (node.Type) { case ControlFlowNodeType.StartNode: @@ -312,7 +371,9 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis throw new InvalidOperationException("Invalid value for DefiniteAssignmentStatus"); } edgeStatus[edge] = newStatus; - nodesWithModifiedInput.Enqueue(edge.To); + DefiniteAssignmentNode targetNode = (DefiniteAssignmentNode)edge.To; + if (analyzedRangeStart <= targetNode.Index && targetNode.Index <= analyzedRangeEnd) + nodesWithModifiedInput.Enqueue(targetNode); } /// @@ -321,7 +382,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// The constant value of the expression; or null if the expression is not a constant. ConstantResolveResult EvaluateConstant(Expression expr) { - return cfgBuilder.EvaluateConstant(expr); + if (resolveVisitor.TypeResolveContext is MinimalResolveContext) { + if (!(expr is PrimitiveExpression || expr is NullReferenceExpression)) + return null; + } + return resolveVisitor.Resolve(expr) as ConstantResolveResult; } /// @@ -330,7 +395,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// The value of the constant boolean expression; or null if the value is not a constant boolean expression. bool? EvaluateCondition(Expression expr) { - return cfgBuilder.EvaluateCondition(expr); + ConstantResolveResult rr = EvaluateConstant(expr); + if (rr != null) + return rr.ConstantValue as bool?; + else + return null; } static DefiniteAssignmentStatus CleanSpecialValues(DefiniteAssignmentStatus status) @@ -633,10 +702,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override DefiniteAssignmentStatus VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, DefiniteAssignmentStatus data) { BlockStatement body = anonymousMethodExpression.Body; - foreach (ControlFlowNode node in analysis.allNodes) { - if (node.NextStatement == body) - analysis.ChangeNodeStatus(node, data); - } + analysis.ChangeNodeStatus(analysis.beginNodeDict[body], data); return data; } @@ -644,10 +710,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis { Statement body = lambdaExpression.Body as Statement; if (body != null) { - foreach (ControlFlowNode node in analysis.allNodes) { - if (node.NextStatement == body) - analysis.ChangeNodeStatus(node, data); - } + analysis.ChangeNodeStatus(analysis.beginNodeDict[body], data); } else { lambdaExpression.Body.AcceptVisitor(this, data); } From ebc34e6a77c4e0e2cbe12f01c7f9bfe512d68ed7 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 20 Mar 2011 02:34:55 +0100 Subject: [PATCH 08/11] Add NextStatement/PreviousStatement properties. --- .../CSharp/Ast/Statements/Statement.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs b/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs index ef01d54b3a..41cbe67f52 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs @@ -38,6 +38,38 @@ namespace ICSharpCode.NRefactory.CSharp } #endregion + /// + /// Gets the previous statement within the current block. + /// This is usually equivalent to , but will skip any non-statements (e.g. comments) + /// + public Statement PreviousStatement { + get { + AstNode node = this; + while ((node = node.PrevSibling) != null) { + Statement stmt = node as Statement; + if (stmt != null) + return stmt; + } + return null; + } + } + + /// + /// Gets the next statement within the current block. + /// This is usually equivalent to , but will skip any non-statements (e.g. comments) + /// + public Statement NextStatement { + get { + AstNode node = this; + while ((node = node.NextSibling) != null) { + Statement stmt = node as Statement; + if (stmt != null) + return stmt; + } + return null; + } + } + public new Statement Clone() { return (Statement)base.Clone(); From 4e1140d08179824359fd6fd0b03bbe6ee5ceccfc Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 20 Mar 2011 14:42:38 +0100 Subject: [PATCH 09/11] Fixed definite assignment analysis bug. --- .../CSharp/Analysis/DefiniteAssignmentAnalysis.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs index fb723aedce..0c9b4ac289 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs @@ -559,11 +559,14 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis { IdentifierExpression ident = left as IdentifierExpression; if (ident != null && ident.Identifier == analysis.variableName) { - right.AcceptVisitor(this, initialStatus); + // right==null is special case when handling 'out' expressions + if (right != null) + right.AcceptVisitor(this, initialStatus); return DefiniteAssignmentStatus.DefinitelyAssigned; } else { DefiniteAssignmentStatus status = left.AcceptVisitor(this, initialStatus); - status = right.AcceptVisitor(this, CleanSpecialValues(status)); + if (right != null) + status = right.AcceptVisitor(this, CleanSpecialValues(status)); return CleanSpecialValues(status); } } From bf965069006e4051ce53699c482364f1124e278a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 20 Mar 2011 15:36:41 +0100 Subject: [PATCH 10/11] DefiniteAssignmentAnalysis bugfix --- .../Analysis/DefiniteAssignmentAnalysis.cs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs index 0c9b4ac289..d4cc6e1c53 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs @@ -123,6 +123,10 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } // Verify that we created nodes for all statements: Debug.Assert(!rootStatement.DescendantsAndSelf.OfType().Except(allNodes.Select(n => n.NextStatement)).Any()); + // Verify that we put all nodes into the dictionaries: + Debug.Assert(rootStatement.DescendantsAndSelf.OfType().All(stmt => beginNodeDict.ContainsKey(stmt))); + Debug.Assert(rootStatement.DescendantsAndSelf.OfType().All(stmt => endNodeDict.ContainsKey(stmt))); + this.analyzedRangeStart = 0; this.analyzedRangeEnd = allNodes.Count - 1; } @@ -166,6 +170,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// Both 'start' and 'end' are inclusive. public void SetAnalyzedRange(Statement start, Statement end) { + Debug.Assert(beginNodeDict.ContainsKey(start) && endNodeDict.ContainsKey(end)); int startIndex = beginNodeDict[start].Index; int endIndex = endNodeDict[end].Index; if (startIndex > endIndex) @@ -353,27 +358,26 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis DefiniteAssignmentStatus oldStatus = edgeStatus[edge]; if (oldStatus == newStatus) return; - // Ensure that status can change only in one direction: - // CodeUnreachable -> PotentiallyAssigned -> DefinitelyAssigned - // Going against this direction indicates a bug and could cause infinite loops. - switch (oldStatus) { - case DefiniteAssignmentStatus.PotentiallyAssigned: - if (newStatus != DefiniteAssignmentStatus.DefinitelyAssigned) - throw new InvalidOperationException("Invalid state transition"); - break; - case DefiniteAssignmentStatus.CodeUnreachable: - if (!(newStatus == DefiniteAssignmentStatus.PotentiallyAssigned || newStatus == DefiniteAssignmentStatus.DefinitelyAssigned)) - throw new InvalidOperationException("Invalid state transition"); - break; - case DefiniteAssignmentStatus.DefinitelyAssigned: - throw new InvalidOperationException("Invalid state transition"); - default: - throw new InvalidOperationException("Invalid value for DefiniteAssignmentStatus"); + // Ensure that status can cannot change back to CodeUnreachable after it once was reachable. + // Also, don't ever use AssignedAfter... for statements. + if (newStatus == DefiniteAssignmentStatus.CodeUnreachable + || newStatus == DefiniteAssignmentStatus.AssignedAfterFalseExpression + || newStatus == DefiniteAssignmentStatus.AssignedAfterTrueExpression) + { + throw new InvalidOperationException(); } + // Note that the status can change from DefinitelyAssigned + // back to PotentiallyAssigned as unreachable input edges are + // discovered to be reachable. + edgeStatus[edge] = newStatus; DefiniteAssignmentNode targetNode = (DefiniteAssignmentNode)edge.To; - if (analyzedRangeStart <= targetNode.Index && targetNode.Index <= analyzedRangeEnd) + if (analyzedRangeStart <= targetNode.Index && targetNode.Index <= analyzedRangeEnd) { + // TODO: potential optimization: visit previously unreachable nodes with higher priority + // (e.g. use Deque and enqueue previously unreachable nodes at the front, but + // other nodes at the end) nodesWithModifiedInput.Enqueue(targetNode); + } } /// From d0228080471304c23dd59e7d8549f6fa75637f45 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 1 Apr 2011 16:43:05 +0200 Subject: [PATCH 11/11] Fixed parsing of enum member declarations. --- .../CSharp/Parser/GeneralScope/TypeDeclarationTests.cs | 9 +++++++++ .../CSharp/Resolver/ResolverTestBase.cs | 2 +- .../ICSharpCode.NRefactory.Tests.csproj | 2 +- ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs | 8 +++----- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/TypeDeclarationTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/TypeDeclarationTests.cs index 8748164503..19183532c8 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/TypeDeclarationTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/TypeDeclarationTests.cs @@ -272,5 +272,14 @@ public abstract class MyClass : MyBase, Interface1, My.Test.Interface2 Assert.AreEqual("N", ns.Name); Assert.AreEqual("MyClass", ((TypeDeclaration)ns.Members.Single()).Name); } + + [Test] + public void EnumWithInitializer() + { + TypeDeclaration td = ParseUtilCSharp.ParseGlobal("enum MyEnum { Val1 = 10 }"); + EnumMemberDeclaration member = (EnumMemberDeclaration)td.Members.Single(); + Assert.AreEqual("Val1", member.Name); + Assert.AreEqual(10, ((PrimitiveExpression)member.Initializer).Value); + } } } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs index db7de9560b..f81a7f2d9f 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs @@ -171,7 +171,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver protected T Resolve(string code) where T : ResolveResult { ResolveResult rr = Resolve(code); - Assert.IsInstanceOf(typeof(T), rr); + Assert.IsTrue(rr is T, "Resolve should be " + typeof(T).Name + ", but was " + (rr != null ? rr.GetType().Name : "null")); return (T)rr; } diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 6d351a08af..3473ad67f3 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -49,7 +49,7 @@ - ..\lib\nunit.framework.dll + ..\..\Mono.Cecil\Test\libs\nunit-2.4.8\nunit.framework.dll diff --git a/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs b/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs index a056c8ba16..0d51ded39e 100644 --- a/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs +++ b/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs @@ -369,17 +369,15 @@ namespace ICSharpCode.NRefactory.CSharp public override void Visit (EnumMember em) { EnumMemberDeclaration newField = new EnumMemberDeclaration (); - VariableInitializer variable = new VariableInitializer (); - - variable.AddChild (new Identifier (em.Name, Convert (em.Location)), AstNode.Roles.Identifier); + // TODO: attributes, 'new' modifier + newField.AddChild (new Identifier (em.Name, Convert (em.Location)), AstNode.Roles.Identifier); if (em.Initializer != null) { var initializer = (Expression)em.Initializer.Accept (this); if (initializer != null) - variable.AddChild (initializer, EnumMemberDeclaration.InitializerRole); + newField.AddChild (initializer, EnumMemberDeclaration.InitializerRole); } - newField.AddChild (variable, AstNode.Roles.Variable); typeStack.Peek ().AddChild (newField, TypeDeclaration.MemberRole); } #endregion