// Copyright (c) 2020 Daniel Grunwald // // 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 System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Reflection.Metadata; using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.TypeSystem { public class FunctionPointerType : AbstractType { public static FunctionPointerType FromSignature(MethodSignature signature, MetadataModule module) { IType returnType = signature.ReturnType; bool returnIsRefReadOnly = false; var customCallConvs = ImmutableArray.CreateBuilder(); while (returnType is ModifiedType modReturn) { if (modReturn.Modifier.IsKnownType(KnownAttribute.In)) { returnType = modReturn.ElementType; returnIsRefReadOnly = true; } else if (modReturn.Modifier.Name.StartsWith("CallConv", StringComparison.Ordinal) && modReturn.Modifier.Namespace == "System.Runtime.CompilerServices") { returnType = modReturn.ElementType; customCallConvs.Add(modReturn.Modifier); } else { break; } } var parameterTypes = ImmutableArray.CreateBuilder(signature.ParameterTypes.Length); var parameterReferenceKinds = ImmutableArray.CreateBuilder(signature.ParameterTypes.Length); foreach (var p in signature.ParameterTypes) { IType paramType = p; ReferenceKind kind = ReferenceKind.None; if (p is ModifiedType modreq) { if (modreq.Modifier.IsKnownType(KnownAttribute.In)) { kind = ReferenceKind.In; paramType = modreq.ElementType; } else if (modreq.Modifier.IsKnownType(KnownAttribute.Out)) { kind = ReferenceKind.Out; paramType = modreq.ElementType; } } if (paramType.Kind == TypeKind.ByReference) { if (kind == ReferenceKind.None) kind = ReferenceKind.Ref; } else { kind = ReferenceKind.None; } parameterTypes.Add(paramType); parameterReferenceKinds.Add(kind); } return new FunctionPointerType( module, signature.Header.CallingConvention, customCallConvs.ToImmutable(), returnType, returnIsRefReadOnly, parameterTypes.MoveToImmutable(), parameterReferenceKinds.MoveToImmutable()); } private readonly MetadataModule module; public readonly SignatureCallingConvention CallingConvention; public readonly ImmutableArray CustomCallingConventions; public readonly IType ReturnType; public readonly bool ReturnIsRefReadOnly; public readonly ImmutableArray ParameterTypes; public readonly ImmutableArray ParameterReferenceKinds; public FunctionPointerType(MetadataModule module, SignatureCallingConvention callingConvention, ImmutableArray customCallingConventions, IType returnType, bool returnIsRefReadOnly, ImmutableArray parameterTypes, ImmutableArray parameterReferenceKinds) { this.module = module; this.CallingConvention = callingConvention; this.CustomCallingConventions = customCallingConventions; this.ReturnType = returnType; this.ReturnIsRefReadOnly = returnIsRefReadOnly; this.ParameterTypes = parameterTypes; this.ParameterReferenceKinds = parameterReferenceKinds; Debug.Assert(parameterTypes.Length == parameterReferenceKinds.Length); } public override string Name => "delegate*"; public override bool? IsReferenceType => false; public override TypeKind Kind => ((module.TypeSystemOptions & TypeSystemOptions.FunctionPointers) != 0) ? TypeKind.FunctionPointer : TypeKind.Struct; public override ITypeDefinition GetDefinition() { if ((module.TypeSystemOptions & TypeSystemOptions.FunctionPointers) != 0) { return null; } else { // If FunctionPointers are not enabled in the TS, we still use FunctionPointerType instances; // but have them act as if they were aliases for UIntPtr. return module.Compilation.FindType(KnownTypeCode.UIntPtr).GetDefinition(); } } public override IType AcceptVisitor(TypeVisitor visitor) { return visitor.VisitFunctionPointerType(this); } public override IType VisitChildren(TypeVisitor visitor) { IType r = ReturnType.AcceptVisitor(visitor); // Keep ta == null as long as no elements changed, allocate the array only if necessary. IType[] pt = (r != ReturnType) ? new IType[ParameterTypes.Length] : null; for (int i = 0; i < ParameterTypes.Length; i++) { IType p = ParameterTypes[i].AcceptVisitor(visitor); if (p == null) throw new NullReferenceException("TypeVisitor.Visit-method returned null"); if (pt == null && p != ParameterTypes[i]) { // we found a difference, so we need to allocate the array pt = new IType[ParameterTypes.Length]; for (int j = 0; j < i; j++) { pt[j] = ParameterTypes[j]; } } if (pt != null) pt[i] = p; } if (pt == null) return this; else return new FunctionPointerType( module, CallingConvention, CustomCallingConventions, r, ReturnIsRefReadOnly, pt.ToImmutableArray(), ParameterReferenceKinds); } public override bool Equals(IType other) { return other is FunctionPointerType fpt && CallingConvention == fpt.CallingConvention && CustomCallingConventions.SequenceEqual(fpt.CustomCallingConventions) && ReturnType.Equals(fpt.ReturnType) && ReturnIsRefReadOnly == fpt.ReturnIsRefReadOnly && ParameterTypes.SequenceEqual(fpt.ParameterTypes) && ParameterReferenceKinds.SequenceEqual(fpt.ParameterReferenceKinds); } public override int GetHashCode() { unchecked { int hash = ReturnType.GetHashCode() ^ CallingConvention.GetHashCode(); foreach (var (p, k) in ParameterTypes.Zip(ParameterReferenceKinds)) { hash ^= p.GetHashCode() ^ k.GetHashCode(); hash *= 8310859; } return hash; } } internal IType WithSignature(IType returnType, ImmutableArray parameterTypes) { return new FunctionPointerType(this.module, this.CallingConvention, this.CustomCallingConventions, returnType, this.ReturnIsRefReadOnly, parameterTypes, this.ParameterReferenceKinds); } } }