From 0aa37bcf44c33bd718e719b884d80e021229fa0c Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 1 Jul 2012 15:24:56 +0200 Subject: [PATCH] Add lazy-loading support to CecilLoader. --- .../ICSharpCode.NRefactory.Tests.csproj | 1 + .../TypeSystem/LazyLoadedCecilLoaderTests.cs | 36 +++ .../TypeSystem/CecilLoader.cs | 239 +++++++++++++++--- .../TypeSystem/ITypeDefinition.cs | 6 + .../AbstractUnresolvedEntity.cs | 2 +- .../DefaultResolvedTypeDefinition.cs | 5 +- 6 files changed, 243 insertions(+), 46 deletions(-) create mode 100644 ICSharpCode.NRefactory.Tests/TypeSystem/LazyLoadedCecilLoaderTests.cs diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 407f7dc19b..9b68806161 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -191,6 +191,7 @@ + diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/LazyLoadedCecilLoaderTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/LazyLoadedCecilLoaderTests.cs new file mode 100644 index 0000000000..2000207509 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/LazyLoadedCecilLoaderTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using ICSharpCode.NRefactory.TypeSystem.Implementation; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.TypeSystem +{ + [TestFixture] + public class LazyLoadedCecilLoaderTests : TypeSystemTests + { + [TestFixtureSetUp] + public void FixtureSetUp() + { + CecilLoader loader = new CecilLoader() { IncludeInternalMembers = true, LazyLoad = true }; + IUnresolvedAssembly pc = loader.LoadAssemblyFile(typeof(TestCase.SimplePublicClass).Assembly.Location); + base.compilation = new SimpleCompilation(pc, CecilLoaderTests.Mscorlib); + } + } +} diff --git a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs index aa60bd995c..8b97a43897 100644 --- a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs +++ b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs @@ -47,6 +47,16 @@ namespace ICSharpCode.NRefactory.TypeSystem /// public bool IncludeInternalMembers { get; set; } + /// + /// Specifies whether to use lazy loading. The default is false. + /// If this property is set to true, the CecilLoader will not copy all the relevant information + /// out of the Cecil object model, but will maintain references to the Cecil objects. + /// This speeds up the loading process and avoids loading unnecessary information, but it causes + /// the Cecil objects to stay in memory (which can significantly increase memory usage). + /// It also prevents serialization of the Cecil-loaded type system. + /// + public bool LazyLoad { get; set; } + /// /// Gets/Sets the documentation provider that is used to retrieve the XML documentation for all members. /// @@ -137,7 +147,7 @@ namespace ICSharpCode.NRefactory.TypeSystem this.CancellationToken.ThrowIfCancellationRequested(); if (this.IncludeInternalMembers || (td.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public) { string name = td.Name; - if (name.Length == 0 || name[0] == '<') + if (name.Length == 0) continue; var t = CreateTopLevelTypeDefinition(td); @@ -166,6 +176,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// This causes ReadTypeReference() to use for references /// in that module. /// + [CLSCompliant(false)] public void SetCurrentModule(ModuleDefinition module) { this.currentModule = module; @@ -1449,65 +1460,76 @@ namespace ICSharpCode.NRefactory.TypeSystem { string name = ReflectionHelper.SplitTypeParameterCountFromReflectionName(typeDefinition.Name); var td = new DefaultUnresolvedTypeDefinition(typeDefinition.Namespace, name); - InitTypeParameters(typeDefinition, td); + if (typeDefinition.HasGenericParameters) + InitTypeParameters(typeDefinition, td.TypeParameters); return td; } - static void InitTypeParameters(TypeDefinition typeDefinition, DefaultUnresolvedTypeDefinition td) + static void InitTypeParameters(TypeDefinition typeDefinition, IList typeParameters) { // Type parameters are initialized within the constructor so that the class can be put into the type storage // before the rest of the initialization runs - this allows it to be available for early binding as soon as possible. for (int i = 0; i < typeDefinition.GenericParameters.Count; i++) { if (typeDefinition.GenericParameters[i].Position != i) throw new InvalidOperationException("g.Position != i"); - td.TypeParameters.Add(new DefaultUnresolvedTypeParameter( + typeParameters.Add(new DefaultUnresolvedTypeParameter( EntityType.TypeDefinition, i, typeDefinition.GenericParameters[i].Name)); } } + void InitTypeParameterConstraints(TypeDefinition typeDefinition, IList typeParameters) + { + for (int i = 0; i < typeParameters.Count; i++) { + AddConstraints((DefaultUnresolvedTypeParameter)typeParameters[i], typeDefinition.GenericParameters[i]); + } + } + void InitTypeDefinition(TypeDefinition typeDefinition, DefaultUnresolvedTypeDefinition td) { + td.Kind = GetTypeKind(typeDefinition); InitTypeModifiers(typeDefinition, td); + InitTypeParameterConstraints(typeDefinition, td.TypeParameters); - if (typeDefinition.HasGenericParameters) { - for (int i = 0; i < typeDefinition.GenericParameters.Count; i++) { - AddConstraints((DefaultUnresolvedTypeParameter)td.TypeParameters[i], typeDefinition.GenericParameters[i]); - } - } - - InitNestedTypes(typeDefinition, td); // nested types can be initialized only after generic parameters were created + // nested types can be initialized only after generic parameters were created + InitNestedTypes(typeDefinition, td, td.NestedTypes); AddAttributes(typeDefinition, td); td.HasExtensionMethods = HasExtensionAttribute(typeDefinition); + InitBaseTypes(typeDefinition, td.BaseTypes); + + td.AddDefaultConstructorIfRequired = (td.Kind == TypeKind.Struct || td.Kind == TypeKind.Enum); + InitMembers(typeDefinition, td, td.Members); + if (HasCecilReferences) + typeSystemTranslationTable[td] = typeDefinition; + if (this.InterningProvider != null) { + td.ApplyInterningProvider(this.InterningProvider); + } + td.Freeze(); + } + + void InitBaseTypes(TypeDefinition typeDefinition, IList baseTypes) + { // set base classes if (typeDefinition.IsEnum) { foreach (FieldDefinition enumField in typeDefinition.Fields) { if (!enumField.IsStatic) { - td.BaseTypes.Add(ReadTypeReference(enumField.FieldType)); + baseTypes.Add(ReadTypeReference(enumField.FieldType)); break; } } } else { if (typeDefinition.BaseType != null) { - td.BaseTypes.Add(ReadTypeReference(typeDefinition.BaseType)); + baseTypes.Add(ReadTypeReference(typeDefinition.BaseType)); } if (typeDefinition.HasInterfaces) { foreach (TypeReference iface in typeDefinition.Interfaces) { - td.BaseTypes.Add(ReadTypeReference(iface)); + baseTypes.Add(ReadTypeReference(iface)); } } } - - InitMembers(typeDefinition, td); - if (HasCecilReferences) - typeSystemTranslationTable[td] = typeDefinition; - if (this.InterningProvider != null) { - td.ApplyInterningProvider(this.InterningProvider); - } - td.Freeze(); } - void InitNestedTypes(TypeDefinition typeDefinition, DefaultUnresolvedTypeDefinition td) + void InitNestedTypes(TypeDefinition typeDefinition, IUnresolvedTypeDefinition declaringTypeDefinition, IList nestedTypes) { if (!typeDefinition.HasNestedTypes) return; @@ -1522,33 +1544,35 @@ namespace ICSharpCode.NRefactory.TypeSystem int pos = name.LastIndexOf('/'); if (pos > 0) name = name.Substring(pos + 1); - if (name.Length == 0 || name[0] == '<') - continue; name = ReflectionHelper.SplitTypeParameterCountFromReflectionName(name); - var nestedType = new DefaultUnresolvedTypeDefinition(td, name); - InitTypeParameters(nestedTypeDef, nestedType); - td.NestedTypes.Add(nestedType); + var nestedType = new DefaultUnresolvedTypeDefinition(declaringTypeDefinition, name); + InitTypeParameters(nestedTypeDef, nestedType.TypeParameters); + nestedTypes.Add(nestedType); InitTypeDefinition(nestedTypeDef, nestedType); } } } - static void InitTypeModifiers(TypeDefinition typeDefinition, DefaultUnresolvedTypeDefinition td) + static TypeKind GetTypeKind(TypeDefinition typeDefinition) { // set classtype if (typeDefinition.IsInterface) { - td.Kind = TypeKind.Interface; + return TypeKind.Interface; } else if (typeDefinition.IsEnum) { - td.Kind = TypeKind.Enum; + return TypeKind.Enum; } else if (typeDefinition.IsValueType) { - td.Kind = TypeKind.Struct; + return TypeKind.Struct; } else if (IsDelegate(typeDefinition)) { - td.Kind = TypeKind.Delegate; + return TypeKind.Delegate; } else if (IsModule(typeDefinition)) { - td.Kind = TypeKind.Module; + return TypeKind.Module; } else { - td.Kind = TypeKind.Class; + return TypeKind.Class; } + } + + static void InitTypeModifiers(TypeDefinition typeDefinition, AbstractUnresolvedEntity td) + { td.IsSealed = typeDefinition.IsSealed; td.IsAbstract = typeDefinition.IsAbstract; switch (typeDefinition.Attributes & TypeAttributes.VisibilityMask) { @@ -1598,9 +1622,8 @@ namespace ICSharpCode.NRefactory.TypeSystem return false; } - void InitMembers(TypeDefinition typeDefinition, DefaultUnresolvedTypeDefinition td) + void InitMembers(TypeDefinition typeDefinition, IUnresolvedTypeDefinition td, IList members) { - td.AddDefaultConstructorIfRequired = (td.Kind == TypeKind.Struct || td.Kind == TypeKind.Enum); if (typeDefinition.HasMethods) { foreach (MethodDefinition method in typeDefinition.Methods) { if (IsVisible(method.Attributes) && !IsAccessor(method.SemanticsAttributes)) { @@ -1611,14 +1634,14 @@ namespace ICSharpCode.NRefactory.TypeSystem else if (method.Name.StartsWith("op_", StringComparison.Ordinal)) type = EntityType.Operator; } - td.Members.Add(ReadMethod(method, td, type)); + members.Add(ReadMethod(method, td, type)); } } } if (typeDefinition.HasFields) { foreach (FieldDefinition field in typeDefinition.Fields) { if (IsVisible(field.Attributes) && !field.IsSpecialName) { - td.Members.Add(ReadField(field, td)); + members.Add(ReadField(field, td)); } } } @@ -1634,14 +1657,14 @@ namespace ICSharpCode.NRefactory.TypeSystem bool setterVisible = property.SetMethod != null && IsVisible(property.SetMethod.Attributes); if (getterVisible || setterVisible) { EntityType type = property.Name == defaultMemberName ? EntityType.Indexer : EntityType.Property; - td.Members.Add(ReadProperty(property, td, type)); + members.Add(ReadProperty(property, td, type)); } } } if (typeDefinition.HasEvents) { foreach (EventDefinition ev in typeDefinition.Events) { if (ev.AddMethod != null && IsVisible(ev.AddMethod.Attributes)) { - td.Members.Add(ReadEvent(ev, td)); + members.Add(ReadEvent(ev, td)); } } } @@ -1653,6 +1676,140 @@ namespace ICSharpCode.NRefactory.TypeSystem } #endregion + #region Lazy-Loaded Type Definition + sealed class LazyCecilTypeDefinition : AbstractUnresolvedEntity, IUnresolvedTypeDefinition + { + readonly CecilLoader loader; + readonly string namespaceName; + readonly TypeDefinition cecilTypeDef; + readonly TypeKind kind; + readonly IList typeParameters; + + IList baseTypes; + IList nestedTypes; + IList members; + + public LazyCecilTypeDefinition(CecilLoader loader, TypeDefinition typeDefinition) + { + this.loader = loader; + this.cecilTypeDef = typeDefinition; + this.EntityType = EntityType.TypeDefinition; + this.namespaceName = cecilTypeDef.Namespace; + this.Name = ReflectionHelper.SplitTypeParameterCountFromReflectionName(cecilTypeDef.Name); + var tps = new List(); + InitTypeParameters(cecilTypeDef, tps); + this.typeParameters = FreezableHelper.FreezeList(tps); + + this.kind = GetTypeKind(typeDefinition); + InitTypeModifiers(typeDefinition, this); + loader.InitTypeParameterConstraints(typeDefinition, typeParameters); + + loader.AddAttributes(typeDefinition, this); + flags[FlagHasExtensionMethods] = HasExtensionAttribute(typeDefinition); + + if (loader.HasCecilReferences) + loader.typeSystemTranslationTable[this] = typeDefinition; + if (loader.InterningProvider != null) { + this.ApplyInterningProvider(loader.InterningProvider); + } + this.Freeze(); + } + + public override string Namespace { + get { return namespaceName; } + set { throw new NotSupportedException(); } + } + + public TypeKind Kind { + get { return kind; } + } + + public IList TypeParameters { + get { return typeParameters; } + } + + public IList BaseTypes { + get { + var result = LazyInit.VolatileRead(ref this.baseTypes); + if (result != null) { + return result; + } else { + result = new List(); + loader.InitBaseTypes(cecilTypeDef, result); + return LazyInit.GetOrSet(ref this.baseTypes, FreezableHelper.FreezeList(result)); + } + } + } + + public IList NestedTypes { + get { + var result = LazyInit.VolatileRead(ref this.nestedTypes); + if (result != null) { + return result; + } else { + result = new List(); + loader.InitNestedTypes(cecilTypeDef, this, result); + return LazyInit.GetOrSet(ref this.nestedTypes, FreezableHelper.FreezeList(result)); + } + } + } + + public IList Members { + get { + var result = LazyInit.VolatileRead(ref this.members); + if (result != null) { + return result; + } else { + result = new List(); + loader.InitMembers(cecilTypeDef, this, result); + return LazyInit.GetOrSet(ref this.members, FreezableHelper.FreezeList(result)); + } + } + } + + public IEnumerable Methods { + get { return Members.OfType(); } + } + + public IEnumerable Properties { + get { return Members.OfType(); } + } + + public IEnumerable Fields { + get { return Members.OfType(); } + } + + public IEnumerable Events { + get { return Members.OfType(); } + } + + public bool AddDefaultConstructorIfRequired { + get { return kind == TypeKind.Struct || kind == TypeKind.Enum; } + } + + public bool? HasExtensionMethods { + get { return flags[FlagHasExtensionMethods]; } + // we always return true or false, never null. + // FlagHasNoExtensionMethods is unused in LazyCecilTypeDefinition + } + + public IType Resolve(ITypeResolveContext context) + { + if (context == null) + throw new ArgumentNullException("context"); + if (context.CurrentAssembly == null) + throw new ArgumentException("An ITypeDefinition cannot be resolved in a context without a current assembly."); + return context.CurrentAssembly.GetTypeDefinition(this) + ?? (IType)new UnknownType(this.Namespace, this.Name, this.TypeParameters.Count); + } + + public ITypeResolveContext CreateResolveContext(ITypeResolveContext parentContext) + { + return parentContext; + } + } + #endregion + #region Read Method [CLSCompliant(false)] public IUnresolvedMethod ReadMethod(MethodDefinition method, IUnresolvedTypeDefinition parentType, EntityType methodType = EntityType.Method) diff --git a/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs b/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs index 508f9ce810..1438505d8b 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ITypeDefinition.cs @@ -48,6 +48,12 @@ namespace ICSharpCode.NRefactory.TypeSystem /// bool? HasExtensionMethods { get; } + /// + /// Gets whether this unresolved type definition causes the addition of a default constructor + /// if no other constructor is present. + /// + bool AddDefaultConstructorIfRequired { get; } + /// /// Looks up the resolved type definition from the corresponding to this unresolved /// type definition. diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractUnresolvedEntity.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractUnresolvedEntity.cs index d0a040e454..66d29b8c0c 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractUnresolvedEntity.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractUnresolvedEntity.cs @@ -50,7 +50,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation internal const ushort FlagShadowing = 0x0008; internal const ushort FlagSynthetic = 0x0010; internal const ushort FlagStatic = 0x0020; - // flags for DefaultUnresolvedTypeDefinition + // flags for DefaultUnresolvedTypeDefinition/LazyCecilTypeDefinition internal const ushort FlagAddDefaultConstructorIfRequired = 0x0040; internal const ushort FlagHasExtensionMethods = 0x0080; internal const ushort FlagHasNoExtensionMethods = 0x0100; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs index c4fb589e9b..4ea28dbe69 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs @@ -322,10 +322,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } } - DefaultUnresolvedTypeDefinition dutd = part as DefaultUnresolvedTypeDefinition; - if (dutd != null) { - addDefaultConstructorIfRequired |= dutd.AddDefaultConstructorIfRequired; - } + addDefaultConstructorIfRequired |= part.AddDefaultConstructorIfRequired; } if (addDefaultConstructorIfRequired) { TypeKind kind = this.Kind;