ILRepack/RepackImporter.cs (731 lines of code) (raw):

// // Copyright (c) 2011 Francois Valdy // Copyright (c) 2015 Timotei Dolean // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; using System; using System.Collections.Generic; using System.Linq; using ILRepacking.Mixins; namespace ILRepacking { internal class RepackImporter : IRepackImporter, IRepackCopier { private readonly ILogger _logger; private readonly IRepackContext _repackContext; private readonly RepackOptions _options; private readonly Dictionary<AssemblyDefinition, int> _aspOffsets; public RepackImporter( ILogger logger, RepackOptions options, IRepackContext repackContext, Dictionary<AssemblyDefinition, int> aspOffsets) { _logger = logger; _options = options; _repackContext = repackContext; _aspOffsets = aspOffsets; } public void Import(ExportedType type, Collection<ExportedType> col, ModuleDefinition module) { var scope = default(IMetadataScope); // try to skip redirects to merged assemblies if (type.Scope is AssemblyNameReference) { if (_repackContext.MergedAssemblies.Any(x => x.Name.Name == ((AssemblyNameReference)type.Scope).Name)) { return; } scope = _repackContext.PlatformFixer.FixPlatformVersion(((AssemblyNameReference)type.Scope)); } else if (type.Scope is ModuleReference) { if (_repackContext.MergedAssemblies.SelectMany(x => x.Modules).Any(x => x.Name == ((ModuleReference)type.Scope).Name)) { return; } // TODO fix scope (should probably be added to target ModuleReferences, otherwise metadatatoken will be wrong) // I've never seen an exported type redirected to a module, doing so would be blind guessing scope = type.Scope; } if (type.IsForwarder) { // Skip duplicated forwarders var fullName = type.FullName; if (col.Any(t => t.IsForwarder && t.FullName == fullName)) { return; } } var nt = new ExportedType(type.Namespace, type.Name, module, scope) { Attributes = type.Attributes, Identifier = type.Identifier, // TODO: CHECK THIS when merging multiple assemblies when exported types ? DeclaringType = type.DeclaringType }; col.Add(nt); } public TypeReference Import(TypeReference reference, IGenericParameterProvider context) { TypeDefinition type = _repackContext.GetMergedTypeFromTypeRef(reference); if (type != null) return type; _repackContext.PlatformFixer.FixPlatformVersion(reference); try { if (context == null) { // we come here when importing types used for assembly-level custom attributes return _repackContext.TargetAssemblyMainModule.ImportReference(reference); } return _repackContext.TargetAssemblyMainModule.ImportReference(reference, context); } catch (ArgumentOutOfRangeException) // working around a bug in Cecil { _logger.Error("Problem adding reference: " + reference.FullName); throw; } } public FieldReference Import(FieldReference reference, IGenericParameterProvider context) { _repackContext.PlatformFixer.FixPlatformVersion(reference); return _repackContext.TargetAssemblyMainModule.ImportReference(reference, context); } public MethodReference Import(MethodReference reference) { _repackContext.PlatformFixer.FixPlatformVersion(reference); return _repackContext.TargetAssemblyMainModule.ImportReference(reference); } public MethodReference Import(MethodReference reference, IGenericParameterProvider context) { // If this is a Method/TypeDefinition, it will be corrected to a definition again later _repackContext.PlatformFixer.FixPlatformVersion(reference); return _repackContext.TargetAssemblyMainModule.ImportReference(reference, context); } public TypeDefinition Import(TypeDefinition type, Collection<TypeDefinition> col, bool internalize) { _logger.Verbose("- Importing " + type); if (ShouldDrop(type)) { return null; } TypeDefinition nt = _repackContext.TargetAssemblyMainModule.GetType(type.FullName); bool justCreatedType = false; if (nt == null) { nt = CreateType(type, col, internalize, null); justCreatedType = true; } else if (DuplicateTypeAllowed(type)) { _logger.Info("Merging " + type); } else if (!type.IsPublic || internalize) { // rename the type previously imported. // renaming the new one before import made Cecil throw an exception. string other = GenerateName(nt); _logger.Info("Renaming " + nt.FullName + " into " + other); nt.Name = other; nt = CreateType(type, col, internalize, null); justCreatedType = true; } else if (_options.UnionMerge) { _logger.Info("Merging " + type); } else { _logger.Error("Duplicate type " + type); throw new InvalidOperationException( "Duplicate type " + type + " from " + type.Scope + ", was also present in " + MappingHandler.GetScopeFullName(_repackContext.MappingHandler.GetOrigTypeScope<IMetadataScope>(nt))); } _repackContext.MappingHandler.StoreRemappedType(type, nt); // nested types first (are never internalized) foreach (TypeDefinition nested in type.NestedTypes) { if (ShouldDrop(nested) == false) { Import(nested, nt.NestedTypes, false); } } foreach (FieldDefinition field in type.Fields) { if (ShouldDrop(field) == false) { CloneTo(field, nt); } } // methods before fields / events foreach (MethodDefinition meth in type.Methods) { if (ShouldDrop(meth) == false) { CloneTo(meth, nt, justCreatedType); } } foreach (EventDefinition evt in type.Events) { if (ShouldDrop(evt) == false) { CloneTo(evt, nt, nt.Events); } } foreach (PropertyDefinition prop in type.Properties) { if (ShouldDrop(prop) == false) { CloneTo(prop, nt, nt.Properties); } } if (internalize && _options.RenameInternalized && !IsModuleTag(nt)) { string newName = GenerateName(nt); _logger.Verbose("Renaming " + nt.FullName + " into " + newName); nt.Name = newName; } return nt; } //Module tag must't be renamed. Otherwise after two repacks .dll will contain <Model> and <Guid><Model> //Assembly.Load() will load <Guid><Module> as type and crash private static bool IsModuleTag(TypeDefinition nt) => nt.FullName == "<Module>"; private string GenerateName(TypeDefinition typeDefinition) { return "<" + Guid.NewGuid() + ">" + typeDefinition.Name; } private bool ShouldDrop<TMember>(TMember member) where TMember : ICustomAttributeProvider, IMemberDefinition { bool hasFilter = String.IsNullOrEmpty(_options.RepackDropAttribute) == false; if (hasFilter == false) { return false; } // skip members marked with a custom attribute named as /repackdrop:RepackDropAttribute var shouldDrop = member.HasCustomAttributes && member.CustomAttributes.FirstOrDefault(attr => attr.AttributeType.Name == _options.RepackDropAttribute) != null; if (shouldDrop) { _logger.Log("Repack dropped " + typeof(TMember).Name + ": " + member.FullName + " as it was marked with " + _options.RepackDropAttribute); } return shouldDrop; } // Real stuff below // // These methods are somehow a merge between the clone methods of Cecil 0.6 and the import ones of 0.9 // They use Cecil's MetaDataImporter to rebase imported stuff into the new assembly, but then another pass is required // to clean the TypeRefs Cecil keeps around (although the generated IL would be kind-o valid without, whatever 'valid' means) /// <summary> /// Clones a field to a newly created type /// </summary> private void CloneTo(FieldDefinition field, TypeDefinition nt) { if (nt.Fields.Any(x => x.Name == field.Name)) { _logger.DuplicateIgnored("field", field); return; } FieldDefinition nf = new FieldDefinition(field.Name, field.Attributes, Import(field.FieldType, nt)); nt.Fields.Add(nf); if (field.HasConstant) nf.Constant = field.Constant; if (field.HasMarshalInfo) nf.MarshalInfo = field.MarshalInfo; if (field.InitialValue != null && field.InitialValue.Length > 0) nf.InitialValue = field.InitialValue; if (field.HasLayoutInfo) nf.Offset = field.Offset; CopyCustomAttributes(field.CustomAttributes, nf.CustomAttributes, nt); } /// <summary> /// Clones a parameter into a newly created method /// </summary> private void CloneTo(ParameterDefinition param, MethodDefinition context, Collection<ParameterDefinition> col) { ParameterDefinition pd = new ParameterDefinition(param.Name, param.Attributes, Import(param.ParameterType, context)); if (param.HasConstant) pd.Constant = param.Constant; if (param.HasMarshalInfo) pd.MarshalInfo = param.MarshalInfo; if (param.HasCustomAttributes) CopyCustomAttributes(param.CustomAttributes, pd.CustomAttributes, context); col.Add(pd); } private void CloneTo(EventDefinition evt, TypeDefinition nt, Collection<EventDefinition> col) { // ignore duplicate event if (nt.Events.Any(x => x.Name == evt.Name)) { _logger.DuplicateIgnored("event", evt); return; } EventDefinition ed = new EventDefinition(evt.Name, evt.Attributes, Import(evt.EventType, nt)); col.Add(ed); if (evt.AddMethod != null) ed.AddMethod = FindMethodInNewType(nt, evt.AddMethod); if (evt.RemoveMethod != null) ed.RemoveMethod = FindMethodInNewType(nt, evt.RemoveMethod); if (evt.InvokeMethod != null) ed.InvokeMethod = FindMethodInNewType(nt, evt.InvokeMethod); if (evt.HasOtherMethods) { foreach (MethodDefinition meth in evt.OtherMethods) { var nm = FindMethodInNewType(nt, meth); if (nm != null) ed.OtherMethods.Add(nm); } } CopyCustomAttributes(evt.CustomAttributes, ed.CustomAttributes, nt); } private void CloneTo(PropertyDefinition prop, TypeDefinition nt, Collection<PropertyDefinition> col) { // ignore duplicate property var others = nt.Properties.Where(x => x.Name == prop.Name).ToList(); if (others.Any()) { bool skip = false; if (!IsIndexer(prop) || !IsIndexer(others.First())) { skip = true; } else { // "Item" property is used to implement Indexer operators // It may be specified more than one, with extra arguments to get/set methods // Note than one may also define a standard "Item" property, in which case he won't be able to define Indexers // Here we try to prevent duplicate indexers, but allow to merge non-duplicated ones (e.g. this[int] & this[string] ) var args = ExtractIndexerParameters(prop); if (others.Any(x => _repackContext.ReflectionHelper.AreSame(args, ExtractIndexerParameters(x)))) { skip = true; } } if (skip) { _logger.DuplicateIgnored("property", prop); return; } } PropertyDefinition pd = new PropertyDefinition(prop.Name, prop.Attributes, Import(prop.PropertyType, nt)); col.Add(pd); if (prop.SetMethod != null) pd.SetMethod = FindMethodInNewType(nt, prop.SetMethod); if (prop.GetMethod != null) pd.GetMethod = FindMethodInNewType(nt, prop.GetMethod); if (prop.HasOtherMethods) { foreach (MethodDefinition meth in prop.OtherMethods) { var nm = FindMethodInNewType(nt, meth); if (nm != null) pd.OtherMethods.Add(nm); } } CopyCustomAttributes(prop.CustomAttributes, pd.CustomAttributes, nt); } private void CloneTo(MethodDefinition meth, TypeDefinition type, bool typeJustCreated) { // ignore duplicate method for merged duplicated types if (!typeJustCreated && type.Methods.Count > 0 && type.Methods.Any(x => (x.Name == meth.Name) && (x.Parameters.Count == meth.Parameters.Count) && (x.ToString() == meth.ToString()))) // TODO: better/faster comparation of parameter types? { _logger.DuplicateIgnored("method", meth); return; } // use void placeholder as we'll do the return type import later on (after generic parameters) MethodDefinition nm = new MethodDefinition(meth.Name, meth.Attributes, _repackContext.TargetAssemblyMainModule.TypeSystem.Void); nm.ImplAttributes = meth.ImplAttributes; if (meth.DebugInformation.HasCustomDebugInformations) nm.DebugInformation.CustomDebugInformations.AddRange(meth.DebugInformation.CustomDebugInformations); if (meth.DebugInformation.HasSequencePoints) nm.DebugInformation.SequencePoints.AddRange(meth.DebugInformation.SequencePoints); type.Methods.Add(nm); CopyGenericParameters(meth.GenericParameters, nm.GenericParameters, nm); if (meth.HasPInvokeInfo) { if (meth.PInvokeInfo == null) { // Even if this was allowed, I'm not sure it'd work out //nm.RVA = meth.RVA; } else { nm.PInvokeInfo = new PInvokeInfo(meth.PInvokeInfo.Attributes, meth.PInvokeInfo.EntryPoint, meth.PInvokeInfo.Module); } } foreach (ParameterDefinition param in meth.Parameters) CloneTo(param, nm, nm.Parameters); foreach (MethodReference ov in meth.Overrides) nm.Overrides.Add(Import(ov, nm)); CopySecurityDeclarations(meth.SecurityDeclarations, nm.SecurityDeclarations, nm); CopyCustomAttributes(meth.CustomAttributes, nm.CustomAttributes, nm); nm.ReturnType = Import(meth.ReturnType, nm); nm.MethodReturnType.Attributes = meth.MethodReturnType.Attributes; if (meth.MethodReturnType.HasConstant) nm.MethodReturnType.Constant = meth.MethodReturnType.Constant; if (meth.MethodReturnType.HasMarshalInfo) nm.MethodReturnType.MarshalInfo = meth.MethodReturnType.MarshalInfo; if (meth.MethodReturnType.HasCustomAttributes) CopyCustomAttributes(meth.MethodReturnType.CustomAttributes, nm.MethodReturnType.CustomAttributes, nm); if (meth.HasBody) CloneTo(meth.Body, nm); meth.Body = null; // frees memory nm.IsAddOn = meth.IsAddOn; nm.IsRemoveOn = meth.IsRemoveOn; nm.IsGetter = meth.IsGetter; nm.IsSetter = meth.IsSetter; nm.CallingConvention = meth.CallingConvention; } private void CloneTo(MethodBody body, MethodDefinition parent) { MethodBody nb = new MethodBody(parent); parent.Body = nb; nb.MaxStackSize = body.MaxStackSize; nb.InitLocals = body.InitLocals; nb.LocalVarToken = body.LocalVarToken; foreach (VariableDefinition var in body.Variables) nb.Variables.Add(new VariableDefinition( Import(var.VariableType, parent))); nb.Instructions.Capacity = Math.Max(nb.Instructions.Capacity, body.Instructions.Count); _repackContext.LineIndexer.PreMethodBodyRepack(body, parent); foreach (Instruction instr in body.Instructions) { Instruction ni; if (instr.OpCode.Code == Code.Calli) { var callSite = (CallSite)instr.Operand; CallSite ncs = new CallSite(Import(callSite.ReturnType, parent)) { HasThis = callSite.HasThis, ExplicitThis = callSite.ExplicitThis, CallingConvention = callSite.CallingConvention }; foreach (ParameterDefinition param in callSite.Parameters) CloneTo(param, parent, ncs.Parameters); ni = Instruction.Create(instr.OpCode, ncs); } else switch (instr.OpCode.OperandType) { case OperandType.InlineArg: case OperandType.ShortInlineArg: if (instr.Operand == body.ThisParameter) { ni = Instruction.Create(instr.OpCode, nb.ThisParameter); } else { int param = body.Method.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni = Instruction.Create(instr.OpCode, parent.Parameters[param]); } break; case OperandType.InlineVar: case OperandType.ShortInlineVar: int var = body.Variables.IndexOf((VariableDefinition)instr.Operand); ni = Instruction.Create(instr.OpCode, nb.Variables[var]); break; case OperandType.InlineField: ni = Instruction.Create(instr.OpCode, Import((FieldReference)instr.Operand, parent)); break; case OperandType.InlineMethod: ni = Instruction.Create(instr.OpCode, Import((MethodReference)instr.Operand, parent)); FixAspNetOffset(nb.Instructions, (MethodReference)instr.Operand, parent); break; case OperandType.InlineType: ni = Instruction.Create(instr.OpCode, Import((TypeReference)instr.Operand, parent)); break; case OperandType.InlineTok: if (instr.Operand is TypeReference) ni = Instruction.Create(instr.OpCode, Import((TypeReference)instr.Operand, parent)); else if (instr.Operand is FieldReference) ni = Instruction.Create(instr.OpCode, Import((FieldReference)instr.Operand, parent)); else if (instr.Operand is MethodReference) ni = Instruction.Create(instr.OpCode, Import((MethodReference)instr.Operand, parent)); else throw new InvalidOperationException(); break; case OperandType.ShortInlineBrTarget: case OperandType.InlineBrTarget: ni = Instruction.Create(instr.OpCode, (Instruction)instr.Operand); break; case OperandType.InlineSwitch: ni = Instruction.Create(instr.OpCode, (Instruction[])instr.Operand); break; case OperandType.InlineR: ni = Instruction.Create(instr.OpCode, (double)instr.Operand); break; case OperandType.ShortInlineR: ni = Instruction.Create(instr.OpCode, (float)instr.Operand); break; case OperandType.InlineNone: ni = Instruction.Create(instr.OpCode); break; case OperandType.InlineString: ni = Instruction.Create(instr.OpCode, (string)instr.Operand); break; case OperandType.ShortInlineI: if (instr.OpCode == OpCodes.Ldc_I4_S) ni = Instruction.Create(instr.OpCode, (sbyte)instr.Operand); else ni = Instruction.Create(instr.OpCode, (byte)instr.Operand); break; case OperandType.InlineI8: ni = Instruction.Create(instr.OpCode, (long)instr.Operand); break; case OperandType.InlineI: ni = Instruction.Create(instr.OpCode, (int)instr.Operand); break; default: throw new InvalidOperationException(); } nb.Instructions.Add(ni); } for (int i = 0; i < body.Instructions.Count; i++) { Instruction instr = nb.Instructions[i]; switch (instr.OpCode.OperandType) { case OperandType.ShortInlineBrTarget: case OperandType.InlineBrTarget: instr.Operand = GetInstruction(body, nb, (Instruction)body.Instructions[i].Operand); break; case OperandType.InlineSwitch: instr.Operand = ((Instruction[])body.Instructions[i].Operand).Select(op => GetInstruction(body, nb, op)).ToArray(); break; default: break; } } foreach (ExceptionHandler eh in body.ExceptionHandlers) { ExceptionHandler neh = new ExceptionHandler(eh.HandlerType); neh.TryStart = GetInstruction(body, nb, eh.TryStart); neh.TryEnd = GetInstruction(body, nb, eh.TryEnd); neh.HandlerStart = GetInstruction(body, nb, eh.HandlerStart); neh.HandlerEnd = GetInstruction(body, nb, eh.HandlerEnd); switch (eh.HandlerType) { case ExceptionHandlerType.Catch: neh.CatchType = Import(eh.CatchType, parent); break; case ExceptionHandlerType.Filter: neh.FilterStart = GetInstruction(body, nb, eh.FilterStart); break; } nb.ExceptionHandlers.Add(neh); } } private TypeDefinition CreateType(TypeDefinition type, Collection<TypeDefinition> col, bool internalize, string rename) { TypeDefinition nt = new TypeDefinition(type.Namespace, rename ?? type.Name, type.Attributes); col.Add(nt); // only top-level types are internalized if (internalize && (nt.DeclaringType == null) && nt.IsPublic) nt.IsPublic = false; CopyGenericParameters(type.GenericParameters, nt.GenericParameters, nt); if (type.BaseType != null) nt.BaseType = Import(type.BaseType, nt); if (type.HasLayoutInfo) { nt.ClassSize = type.ClassSize; nt.PackingSize = type.PackingSize; } // don't copy these twice if UnionMerge==true // TODO: we can move this down if we chek for duplicates when adding CopySecurityDeclarations(type.SecurityDeclarations, nt.SecurityDeclarations, nt); CopyInterfaces(type.Interfaces, nt.Interfaces, nt); CopyCustomAttributes(type.CustomAttributes, nt.CustomAttributes, nt); return nt; } private void CopyInterfaces(Collection<InterfaceImplementation> interfaces1, Collection<InterfaceImplementation> interfaces2, TypeDefinition nt) { foreach (var iface in interfaces1) { var newIface = new InterfaceImplementation(Import(iface.InterfaceType, nt)); CopyCustomAttributes(iface.CustomAttributes, newIface.CustomAttributes, nt); interfaces2.Add(newIface); } } private MethodDefinition FindMethodInNewType(TypeDefinition nt, MethodDefinition methodDefinition) { var ret = _repackContext.ReflectionHelper.FindMethodDefinitionInType(nt, methodDefinition); if (ret == null) { _logger.Warn("Method '" + methodDefinition.FullName + "' not found in merged type '" + nt.FullName + "'"); } return ret; } private void FixAspNetOffset(Collection<Instruction> instructions, MethodReference operand, MethodDefinition parent) { if (operand.Name == "WriteUTF8ResourceString" || operand.Name == "CreateResourceBasedLiteralControl") { var fullName = operand.FullName; if (fullName == "System.Void System.Web.UI.TemplateControl::WriteUTF8ResourceString(System.Web.UI.HtmlTextWriter,System.Int32,System.Int32,System.Boolean)" || fullName == "System.Web.UI.LiteralControl System.Web.UI.TemplateControl::CreateResourceBasedLiteralControl(System.Int32,System.Int32,System.Boolean)") { int offset; if (_aspOffsets.TryGetValue(parent.Module.Assembly, out offset)) { int prev = (int)instructions[instructions.Count - 4].Operand; instructions[instructions.Count - 4].Operand = prev + offset; } } } } private static bool IsIndexer(PropertyDefinition prop) { if (prop.Name != "Item") return false; var parameters = ExtractIndexerParameters(prop); return parameters != null && parameters.Count > 0; } private static IList<ParameterDefinition> ExtractIndexerParameters(PropertyDefinition prop) { if (prop.GetMethod != null) return prop.GetMethod.Parameters; if (prop.SetMethod != null) return prop.SetMethod.Parameters.ToList().GetRange(0, prop.SetMethod.Parameters.Count - 1); return null; } private static Instruction GetInstruction(MethodBody oldBody, MethodBody newBody, Instruction i) { int pos = oldBody.Instructions.IndexOf(i); if (pos > -1 && pos < newBody.Instructions.Count) return newBody.Instructions[pos]; return null /*newBody.Instructions.Outside*/; } private bool DuplicateTypeAllowed(TypeDefinition type) { string fullName = type.FullName; // Merging module because IKVM uses this class to store some fields. // Doesn't fully work yet, as IKVM is nice enough to give all the fields the same name... if (fullName == "<Module>" || fullName == "__<Proxy>") return true; // XAML helper class, identical in all assemblies, unused within the assembly, and instanciated through reflection from the outside // We could just skip them after the first one, but merging them works just fine if (fullName == "XamlGeneratedNamespace.GeneratedInternalTypeHelper") return true; // Merge should be OK since member's names are pretty unique, // but renaming duplicate members would be safer... if (fullName == "<PrivateImplementationDetails>" && type.IsPublic) return true; if (_options.AllowedDuplicateTypes.Contains(fullName)) return true; var top = type; while (top.IsNested) top = top.DeclaringType; string nameSpace = top.Namespace; if (!String.IsNullOrEmpty(nameSpace) && _options.AllowedDuplicateNameSpaces.Any(s => s == nameSpace || nameSpace.StartsWith(s + "."))) return true; return false; } /// <summary> /// Clones a collection of SecurityDeclarations /// </summary> public void CopySecurityDeclarations(Collection<SecurityDeclaration> input, Collection<SecurityDeclaration> output, IGenericParameterProvider context) { foreach (SecurityDeclaration sec in input) { SecurityDeclaration newSec = null; if (PermissionsetHelper.IsXmlPermissionSet(sec)) { newSec = PermissionsetHelper.Xml2PermissionSet(sec, _repackContext.TargetAssemblyMainModule); } if (newSec == null) { newSec = new SecurityDeclaration(sec.Action); foreach (SecurityAttribute sa in sec.SecurityAttributes) { SecurityAttribute newSa = new SecurityAttribute(Import(sa.AttributeType, context)); if (sa.HasFields) { foreach (CustomAttributeNamedArgument cana in sa.Fields) { newSa.Fields.Add(Copy(cana, context)); } } if (sa.HasProperties) { foreach (CustomAttributeNamedArgument cana in sa.Properties) { newSa.Properties.Add(Copy(cana, context)); } } newSec.SecurityAttributes.Add(newSa); } } output.Add(newSec); } } // helper private static void Copy<T>(Collection<T> input, Collection<T> output, Action<T, T> action) { if (input.Count != output.Count) throw new InvalidOperationException(); for (int i = 0; i < input.Count; i++) { action.Invoke(input[i], output[i]); } } public void CopyGenericParameters(Collection<GenericParameter> input, Collection<GenericParameter> output, IGenericParameterProvider nt) { foreach (GenericParameter gp in input) { GenericParameter ngp = new GenericParameter(gp.Name, nt); ngp.Attributes = gp.Attributes; output.Add(ngp); } // delay copy to ensure all generics parameters are already present Copy(input, output, (gp, ngp) => CopyTypeReferences(gp.Constraints, ngp.Constraints, nt)); Copy(input, output, (gp, ngp) => CopyCustomAttributes(gp.CustomAttributes, ngp.CustomAttributes, nt)); } public void CopyCustomAttributes(Collection<CustomAttribute> input, Collection<CustomAttribute> output, IGenericParameterProvider context) { CopyCustomAttributes(input, output, true, context); } public CustomAttribute Copy(CustomAttribute ca, IGenericParameterProvider context) { CustomAttribute newCa = new CustomAttribute(Import(ca.Constructor)); foreach (var arg in ca.ConstructorArguments) newCa.ConstructorArguments.Add(Copy(arg, context)); foreach (var arg in ca.Fields) newCa.Fields.Add(Copy(arg, context)); foreach (var arg in ca.Properties) newCa.Properties.Add(Copy(arg, context)); return newCa; } public void CopyCustomAttributes(Collection<CustomAttribute> input, Collection<CustomAttribute> output, bool allowMultiple, IGenericParameterProvider context) { var reflectionHelper = _repackContext.ReflectionHelper; foreach (CustomAttribute ca in input) { var caType = ca.AttributeType; var similarAttributes = output.Where(attr => reflectionHelper.AreSame(attr.AttributeType, caType)).ToList(); if (similarAttributes.Count != 0) { if (!allowMultiple) continue; if (!CustomAttributeTypeAllowsMultiple(caType)) continue; if (similarAttributes.Any(x => reflectionHelper.AreSame(x.ConstructorArguments, ca.ConstructorArguments) && reflectionHelper.AreSame(x.Fields, ca.Fields) && reflectionHelper.AreSame(x.Properties, ca.Properties) )) continue; } output.Add(Copy(ca, context)); } } private bool CustomAttributeTypeAllowsMultiple(TypeReference type) { if (type.FullName == "IKVM.Attributes.JavaModuleAttribute" || type.FullName == "IKVM.Attributes.PackageListAttribute") { // IKVM module attributes, although they don't allow multiple, IKVM supports the attribute being specified multiple times return true; } TypeDefinition typeDef = type.Resolve(); if (typeDef != null) { var ca = typeDef.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == "System.AttributeUsageAttribute"); if (ca != null) { var prop = ca.Properties.FirstOrDefault(y => y.Name == "AllowMultiple"); if (prop.Argument.Value is bool) { return (bool)prop.Argument.Value; } } } // default is false return false; } public void CopyTypeReferences(Collection<TypeReference> input, Collection<TypeReference> output, IGenericParameterProvider context) { foreach (TypeReference ta in input) { output.Add(Import(ta, context)); } } public void CopyTypeReferences(Collection<GenericParameterConstraint> input, Collection<GenericParameterConstraint> output, IGenericParameterProvider context) { foreach (var gpc in input) { var result = new GenericParameterConstraint(Import(gpc.ConstraintType, context)); CopyCustomAttributes(gpc.CustomAttributes, result.CustomAttributes, context); output.Add(result); } } public CustomAttributeArgument Copy(CustomAttributeArgument arg, IGenericParameterProvider context) { return new CustomAttributeArgument(Import(arg.Type, context), ImportCustomAttributeValue(arg.Value, context)); } public CustomAttributeNamedArgument Copy(CustomAttributeNamedArgument namedArg, IGenericParameterProvider context) { return new CustomAttributeNamedArgument(namedArg.Name, Copy(namedArg.Argument, context)); } private object ImportCustomAttributeValue(object obj, IGenericParameterProvider context) { if (obj is TypeReference) return Import((TypeReference)obj, context); if (obj is CustomAttributeArgument) return Copy((CustomAttributeArgument)obj, context); if (obj is CustomAttributeArgument[]) return Array.ConvertAll((CustomAttributeArgument[])obj, a => Copy(a, context)); return obj; } } }