diff --git a/src/LibProtodec/AssemblyInspector.cs b/src/LibProtodec/AssemblyInspector.cs deleted file mode 100644 index fbeb64c..0000000 --- a/src/LibProtodec/AssemblyInspector.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright © 2023-2024 Xpl0itR -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace LibProtodec; - -public sealed class AssemblyInspector : IDisposable -{ - public readonly MetadataLoadContext AssemblyContext; - public readonly IReadOnlyList LoadedTypes; - - public AssemblyInspector(string assemblyPath) - { - bool isFile = File.Exists(assemblyPath); - string assemblyDir = isFile - ? Path.GetDirectoryName(assemblyPath)! - : assemblyPath; - - PermissiveAssemblyResolver assemblyResolver = new( - Directory.EnumerateFiles(assemblyDir, searchPattern: "*.dll")); - - AssemblyContext = new MetadataLoadContext(assemblyResolver); - LoadedTypes = isFile - ? AssemblyContext.LoadFromAssemblyPath(assemblyPath).GetTypes() - : assemblyResolver.AssemblyPathLookup.Values.SelectMany(path => AssemblyContext.LoadFromAssemblyPath(path).GetTypes()).ToList(); - } - - public IEnumerable GetProtobufMessageTypes() - { - Type? iMessage = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Google.Protobuf.IMessage", null) - ?? AssemblyContext.LoadFromAssemblyName("Google.Protobuf") - .GetType("Google.Protobuf.IMessage"); - - return LoadedTypes.Where( - type => type is { IsNested: false, IsSealed: true } - && type.Namespace?.StartsWith("Google.Protobuf", StringComparison.Ordinal) != true - && type.IsAssignableTo(iMessage)); - } - - public IEnumerable GetProtobufServiceClientTypes() - { - Type? clientBase = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Grpc.Core.ClientBase", null) - ?? AssemblyContext.LoadFromAssemblyName("Grpc.Core.Api") - .GetType("Grpc.Core.ClientBase"); - - return LoadedTypes.Where( - type => type is { IsNested: true, IsAbstract: false } - && type.IsAssignableTo(clientBase)); - } - - public IEnumerable GetProtobufServiceServerTypes() - { - Type? bindServiceMethodAttribute = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Grpc.Core.BindServiceMethodAttribute", null) - ?? AssemblyContext.LoadFromAssemblyName("Grpc.Core.Api") - .GetType("Grpc.Core.BindServiceMethodAttribute"); - - return LoadedTypes.Where( - type => type is { IsNested: true, IsAbstract: true, DeclaringType: { IsNested: false, IsSealed: true, IsAbstract: true } } - && type.GetCustomAttributesData().Any(attribute => attribute.AttributeType == bindServiceMethodAttribute)); - } - - public void Dispose() => - AssemblyContext.Dispose(); - - /// - /// An assembly resolver that uses paths to every assembly that may be loaded. - /// The file name is expected to be the same as the assembly's simple name (casing ignored). - /// PublicKeyToken, Version and CultureName are ignored. - /// - private sealed class PermissiveAssemblyResolver(IEnumerable assemblyPaths) : MetadataAssemblyResolver - { - public readonly IReadOnlyDictionary AssemblyPathLookup = - assemblyPaths.ToDictionary( - static path => Path.GetFileNameWithoutExtension(path), - StringComparer.OrdinalIgnoreCase); - - /// - public override Assembly? Resolve(MetadataLoadContext mlc, AssemblyName assemblyName) => - AssemblyPathLookup.TryGetValue(assemblyName.Name!, out string? assemblyPath) - ? mlc.LoadFromAssemblyPath(assemblyPath) - : null; - } -} \ No newline at end of file diff --git a/src/LibProtodec/Loaders/ClrAssemblyLoader.cs b/src/LibProtodec/Loaders/ClrAssemblyLoader.cs new file mode 100644 index 0000000..01abb22 --- /dev/null +++ b/src/LibProtodec/Loaders/ClrAssemblyLoader.cs @@ -0,0 +1,112 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using CommunityToolkit.Diagnostics; +using LibProtodec.Models.Cil; +using LibProtodec.Models.Cil.Clr; + +namespace LibProtodec.Loaders; + +public sealed class ClrAssemblyLoader : ICilAssemblyLoader +{ + public readonly MetadataLoadContext LoadContext; + + public ClrAssemblyLoader(string assemblyPath) + { + bool isFile = File.Exists(assemblyPath); + string assemblyDir = isFile + ? Path.GetDirectoryName(assemblyPath)! + : assemblyPath; + + PermissiveAssemblyResolver assemblyResolver = new( + Directory.EnumerateFiles(assemblyDir, searchPattern: "*.dll")); + + LoadContext = new MetadataLoadContext(assemblyResolver); + LoadedTypes = isFile + ? LoadContext.LoadFromAssemblyPath(assemblyPath) + .GetTypes() + .Select(ClrType.GetOrCreate) + .ToList() + : assemblyResolver.AssemblyPathLookup.Values + .SelectMany(path => LoadContext.LoadFromAssemblyPath(path).GetTypes()) + .Select(ClrType.GetOrCreate) + .ToList(); + } + + public IReadOnlyList LoadedTypes { get; } + + public ICilType IMessage + { + get + { + ICilType? iMessage = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Google.Protobuf.IMessage", null); + if (iMessage is not null) + return iMessage; + + Type? iMessageType = LoadContext.LoadFromAssemblyName("Google.Protobuf").GetType("Google.Protobuf.IMessage"); + Guard.IsNotNull(iMessageType); + + return ClrType.GetOrCreate(iMessageType); + } + } + + public ICilType ClientBase + { + get + { + ICilType? clientBase = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Grpc.Core.ClientBase", null); + if (clientBase is not null) + return clientBase; + + Type? clientBaseType = LoadContext.LoadFromAssemblyName("Grpc.Core.Api").GetType("Grpc.Core.ClientBase"); + Guard.IsNotNull(clientBaseType); + + return ClrType.GetOrCreate(clientBaseType); + } + } + + public ICilType BindServiceMethodAttribute + { + get + { + ICilType? attribute = LoadedTypes.SingleOrDefault(static type => type?.FullName == "Grpc.Core.BindServiceMethodAttribute", null); + if (attribute is not null) + return attribute; + + Type? attributeType = LoadContext.LoadFromAssemblyName("Grpc.Core.Api").GetType("Grpc.Core.BindServiceMethodAttribute"); + Guard.IsNotNull(attributeType); + + return ClrType.GetOrCreate(attributeType); + } + } + + public void Dispose() => + LoadContext.Dispose(); + + /// + /// An assembly resolver that uses paths to every assembly that may be loaded. + /// The file name is expected to be the same as the assembly's simple name (casing ignored). + /// PublicKeyToken, Version and CultureName are ignored. + /// + private sealed class PermissiveAssemblyResolver(IEnumerable assemblyPaths) : MetadataAssemblyResolver + { + public readonly IReadOnlyDictionary AssemblyPathLookup = + assemblyPaths.ToDictionary( + static path => Path.GetFileNameWithoutExtension(path), + StringComparer.OrdinalIgnoreCase); + + /// + public override Assembly? Resolve(MetadataLoadContext mlc, AssemblyName assemblyName) => + AssemblyPathLookup.TryGetValue(assemblyName.Name!, out string? assemblyPath) + ? mlc.LoadFromAssemblyPath(assemblyPath) + : null; + } +} \ No newline at end of file diff --git a/src/LibProtodec/Loaders/ICilAssemblyLoader.cs b/src/LibProtodec/Loaders/ICilAssemblyLoader.cs new file mode 100644 index 0000000..5069e4c --- /dev/null +++ b/src/LibProtodec/Loaders/ICilAssemblyLoader.cs @@ -0,0 +1,23 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; +using LibProtodec.Models.Cil; + +namespace LibProtodec.Loaders; + +public interface ICilAssemblyLoader : IDisposable +{ + IReadOnlyList LoadedTypes { get; } + + // ReSharper disable once InconsistentNaming + ICilType IMessage { get; } + + ICilType ClientBase { get; } + + ICilType BindServiceMethodAttribute { get; } +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs b/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs new file mode 100644 index 0000000..3b3ef1a --- /dev/null +++ b/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs @@ -0,0 +1,42 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace LibProtodec.Models.Cil.Clr; + +public sealed class ClrAttribute(CustomAttributeData clrAttribute) : ICilAttribute +{ + private IList? _constructorArguments; + + public ICilType Type => + ClrType.GetOrCreate(clrAttribute.AttributeType); + + public IList ConstructorArguments + { + get + { + if (_constructorArguments is null) + { + IList args = clrAttribute.ConstructorArguments; + + if (args.Count < 1) + { + _constructorArguments = Array.Empty(); + } + else + { + _constructorArguments = args.Select(static arg => arg.Value).ToList(); + } + } + + return _constructorArguments; + } + } +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrField.cs b/src/LibProtodec/Models/Cil/Clr/ClrField.cs new file mode 100644 index 0000000..b1f33bb --- /dev/null +++ b/src/LibProtodec/Models/Cil/Clr/ClrField.cs @@ -0,0 +1,24 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Reflection; + +namespace LibProtodec.Models.Cil.Clr; + +public sealed class ClrField(FieldInfo clrField) : ClrMember(clrField), ICilField +{ + public object? ConstantValue => + clrField.GetRawConstantValue(); + + public bool IsLiteral => + clrField.IsLiteral; + + public bool IsPublic => + clrField.IsPublic; + + public bool IsStatic => + clrField.IsStatic; +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrMember.cs b/src/LibProtodec/Models/Cil/Clr/ClrMember.cs new file mode 100644 index 0000000..289e01b --- /dev/null +++ b/src/LibProtodec/Models/Cil/Clr/ClrMember.cs @@ -0,0 +1,33 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; +using System.Reflection; + +namespace LibProtodec.Models.Cil.Clr; + +public abstract class ClrMember(MemberInfo clrMember) +{ + public string Name => + clrMember.Name; + + public bool IsInherited => + clrMember.DeclaringType != clrMember.ReflectedType; + + public ICilType? DeclaringType => + clrMember.DeclaringType is null + ? null + : ClrType.GetOrCreate( + clrMember.DeclaringType); + + public IEnumerable GetCustomAttributes() + { + foreach (CustomAttributeData attribute in clrMember.GetCustomAttributesData()) + { + yield return new ClrAttribute(attribute); + } + } +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs b/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs new file mode 100644 index 0000000..0dcbfd5 --- /dev/null +++ b/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs @@ -0,0 +1,37 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; +using System.Reflection; + +namespace LibProtodec.Models.Cil.Clr; + +public sealed class ClrMethod(MethodInfo clrMethod) : ClrMember(clrMethod), ICilMethod +{ + public bool IsPublic => + clrMethod.IsPublic; + + public bool IsNonPublic => + (clrMethod.Attributes & MethodAttributes.Public) == 0; + + public bool IsStatic => + clrMethod.IsStatic; + + public bool IsVirtual => + clrMethod.IsVirtual; + + public ICilType ReturnType => + ClrType.GetOrCreate(clrMethod.ReturnType); + + public IEnumerable GetParameterTypes() + { + foreach (ParameterInfo parameter in clrMethod.GetParameters()) + { + yield return ClrType.GetOrCreate( + parameter.ParameterType); + } + } +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrProperty.cs b/src/LibProtodec/Models/Cil/Clr/ClrProperty.cs new file mode 100644 index 0000000..c249792 --- /dev/null +++ b/src/LibProtodec/Models/Cil/Clr/ClrProperty.cs @@ -0,0 +1,32 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Reflection; + +namespace LibProtodec.Models.Cil.Clr; + +public sealed class ClrProperty(PropertyInfo clrProperty) : ClrMember(clrProperty), ICilProperty +{ + public ICilType Type => + ClrType.GetOrCreate( + clrProperty.PropertyType); + + public bool CanRead => + clrProperty.CanRead; + + public bool CanWrite => + clrProperty.CanWrite; + + public ICilMethod? Getter => + CanRead + ? new ClrMethod(clrProperty.GetMethod!) + : null; + + public ICilMethod? Setter => + CanWrite + ? new ClrMethod(clrProperty.SetMethod!) + : null; +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrType.cs b/src/LibProtodec/Models/Cil/Clr/ClrType.cs new file mode 100644 index 0000000..53dac65 --- /dev/null +++ b/src/LibProtodec/Models/Cil/Clr/ClrType.cs @@ -0,0 +1,129 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using CommunityToolkit.Diagnostics; + +namespace LibProtodec.Models.Cil.Clr; + +public sealed class ClrType : ClrMember, ICilType +{ + private const BindingFlags Everything = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + + private readonly Type _clrType; + private IList? _genericTypeArguments; + + private ClrType(Type clrType) : base(clrType) => + _clrType = clrType; + + public string FullName => + _clrType.FullName ?? _clrType.Name; + + public string? Namespace => + _clrType.Namespace; + + public string DeclaringAssemblyName => + _clrType.Assembly.FullName + ?? ThrowHelper.ThrowArgumentNullException( + nameof(_clrType.Assembly.FullName)); + + public ICilType? BaseType => + _clrType.BaseType is null + ? null + : GetOrCreate(_clrType.BaseType); + + public bool IsAbstract => + _clrType.IsAbstract; + + public bool IsClass => + _clrType.IsClass; + + public bool IsEnum => + _clrType.IsEnum; + + public bool IsNested => + _clrType.IsNested; + + public bool IsSealed => + _clrType.IsSealed; + + public IList GenericTypeArguments + { + get + { + if (_genericTypeArguments is null) + { + Type[] args = _clrType.GenericTypeArguments; + + if (args.Length < 1) + { + _genericTypeArguments = Array.Empty(); + } + else + { + _genericTypeArguments = args.Select(GetOrCreate).ToList(); + } + } + + return _genericTypeArguments; + } + } + + public IEnumerable GetFields() + { + foreach (FieldInfo field in _clrType.GetFields(Everything)) + { + yield return new ClrField(field); + } + } + + public IEnumerable GetMethods() + { + foreach (MethodInfo method in _clrType.GetMethods(Everything)) + { + yield return new ClrMethod(method); + } + } + + public IEnumerable GetNestedTypes() + { + foreach (Type type in _clrType.GetNestedTypes(Everything)) + { + yield return GetOrCreate(type); + } + } + + public IEnumerable GetProperties() + { + foreach (PropertyInfo property in _clrType.GetProperties(Everything)) + { + yield return new ClrProperty(property); + } + } + + public bool IsAssignableTo(ICilType type) + { + if (type is ClrType clrType) + { + return _clrType.IsAssignableTo(clrType._clrType); + } + + return ThrowHelper.ThrowNotSupportedException(); + } + + + private static readonly ConcurrentDictionary TypeLookup = []; + + public static ICilType GetOrCreate(Type clrType) => + TypeLookup.GetOrAdd( + clrType.FullName ?? clrType.Name, + static (_, clrType) => new ClrType(clrType), + clrType); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilAttribute.cs b/src/LibProtodec/Models/Cil/ICilAttribute.cs new file mode 100644 index 0000000..76d17e1 --- /dev/null +++ b/src/LibProtodec/Models/Cil/ICilAttribute.cs @@ -0,0 +1,16 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; + +namespace LibProtodec.Models.Cil; + +public interface ICilAttribute +{ + ICilType Type { get; } + + IList ConstructorArguments { get; } +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilField.cs b/src/LibProtodec/Models/Cil/ICilField.cs new file mode 100644 index 0000000..5b511e7 --- /dev/null +++ b/src/LibProtodec/Models/Cil/ICilField.cs @@ -0,0 +1,22 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; + +namespace LibProtodec.Models.Cil; + +public interface ICilField +{ + string Name { get; } + + object? ConstantValue { get; } + + bool IsLiteral { get; } + bool IsPublic { get; } + bool IsStatic { get; } + + IEnumerable GetCustomAttributes(); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilMethod.cs b/src/LibProtodec/Models/Cil/ICilMethod.cs new file mode 100644 index 0000000..7c4454c --- /dev/null +++ b/src/LibProtodec/Models/Cil/ICilMethod.cs @@ -0,0 +1,25 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; + +namespace LibProtodec.Models.Cil; + +public interface ICilMethod +{ + string Name { get; } + + bool IsInherited { get; } + bool IsPublic { get; } + bool IsStatic { get; } + bool IsVirtual { get; } + + ICilType ReturnType { get; } + + IEnumerable GetCustomAttributes(); + + IEnumerable GetParameterTypes(); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilProperty.cs b/src/LibProtodec/Models/Cil/ICilProperty.cs new file mode 100644 index 0000000..15b8a20 --- /dev/null +++ b/src/LibProtodec/Models/Cil/ICilProperty.cs @@ -0,0 +1,24 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; + +namespace LibProtodec.Models.Cil; + +public interface ICilProperty +{ + string Name { get; } + ICilType Type { get; } + + bool IsInherited { get; } + bool CanRead { get; } + bool CanWrite { get; } + + ICilMethod? Getter { get; } + ICilMethod? Setter { get; } + + IEnumerable GetCustomAttributes(); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilType.cs b/src/LibProtodec/Models/Cil/ICilType.cs new file mode 100644 index 0000000..8c94bfb --- /dev/null +++ b/src/LibProtodec/Models/Cil/ICilType.cs @@ -0,0 +1,40 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; + +namespace LibProtodec.Models.Cil; + +public interface ICilType +{ + string Name { get; } + string FullName { get; } + string? Namespace { get; } + + string DeclaringAssemblyName { get; } + ICilType? DeclaringType { get; } + ICilType? BaseType { get; } + + bool IsAbstract { get; } + bool IsClass { get; } + bool IsEnum { get; } + bool IsNested { get; } + bool IsSealed { get; } + + IList GenericTypeArguments { get; } + + IEnumerable GetCustomAttributes(); + + IEnumerable GetFields(); + + IEnumerable GetMethods(); + + IEnumerable GetNestedTypes(); + + IEnumerable GetProperties(); + + bool IsAssignableTo(ICilType type); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Fields/EnumField.cs b/src/LibProtodec/Models/Protobuf/Fields/EnumField.cs similarity index 93% rename from src/LibProtodec/Models/Fields/EnumField.cs rename to src/LibProtodec/Models/Protobuf/Fields/EnumField.cs index 6e3fa13..bf7b615 100644 --- a/src/LibProtodec/Models/Fields/EnumField.cs +++ b/src/LibProtodec/Models/Protobuf/Fields/EnumField.cs @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -namespace LibProtodec.Models.Fields; +namespace LibProtodec.Models.Protobuf.Fields; public sealed class EnumField { diff --git a/src/LibProtodec/Models/Fields/MessageField.cs b/src/LibProtodec/Models/Protobuf/Fields/MessageField.cs similarity index 75% rename from src/LibProtodec/Models/Fields/MessageField.cs rename to src/LibProtodec/Models/Protobuf/Fields/MessageField.cs index 33e7adb..96c2cbe 100644 --- a/src/LibProtodec/Models/Fields/MessageField.cs +++ b/src/LibProtodec/Models/Protobuf/Fields/MessageField.cs @@ -5,16 +5,16 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. using System.IO; -using LibProtodec.Models.TopLevels; -using LibProtodec.Models.Types; +using LibProtodec.Models.Protobuf.TopLevels; +using LibProtodec.Models.Protobuf.Types; -namespace LibProtodec.Models.Fields; +namespace LibProtodec.Models.Protobuf.Fields; public sealed class MessageField { - public required IType Type { get; init; } - public required string Name { get; init; } - public required int Id { get; init; } + public required IProtobufType Type { get; init; } + public required string Name { get; init; } + public required int Id { get; init; } public bool IsObsolete { get; init; } public bool HasHasProp { get; init; } diff --git a/src/LibProtodec/Models/Fields/ServiceMethod.cs b/src/LibProtodec/Models/Protobuf/Fields/ServiceMethod.cs similarity index 80% rename from src/LibProtodec/Models/Fields/ServiceMethod.cs rename to src/LibProtodec/Models/Protobuf/Fields/ServiceMethod.cs index f1c10e1..48cbca2 100644 --- a/src/LibProtodec/Models/Fields/ServiceMethod.cs +++ b/src/LibProtodec/Models/Protobuf/Fields/ServiceMethod.cs @@ -5,16 +5,16 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. using System.CodeDom.Compiler; -using LibProtodec.Models.TopLevels; -using LibProtodec.Models.Types; +using LibProtodec.Models.Protobuf.TopLevels; +using LibProtodec.Models.Protobuf.Types; -namespace LibProtodec.Models.Fields; +namespace LibProtodec.Models.Protobuf.Fields; public sealed class ServiceMethod { - public required string Name { get; init; } - public required IType RequestType { get; init; } - public required IType ResponseType { get; init; } + public required string Name { get; init; } + public required IProtobufType RequestType { get; init; } + public required IProtobufType ResponseType { get; init; } public bool IsRequestStreamed { get; init; } public bool IsResponseStreamed { get; init; } diff --git a/src/LibProtodec/Models/Protobuf.cs b/src/LibProtodec/Models/Protobuf/Protobuf.cs similarity index 91% rename from src/LibProtodec/Models/Protobuf.cs rename to src/LibProtodec/Models/Protobuf/Protobuf.cs index 1f75aa4..37ee6c8 100644 --- a/src/LibProtodec/Models/Protobuf.cs +++ b/src/LibProtodec/Models/Protobuf/Protobuf.cs @@ -8,10 +8,10 @@ using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Linq; -using LibProtodec.Models.TopLevels; -using LibProtodec.Models.Types; +using LibProtodec.Models.Protobuf.TopLevels; +using LibProtodec.Models.Protobuf.Types; -namespace LibProtodec.Models; +namespace LibProtodec.Models.Protobuf; public sealed class Protobuf { @@ -90,7 +90,7 @@ public sealed class Protobuf writer.WriteLine(';'); } - public static void WriteTypeNameTo(TextWriter writer, IType type, TopLevel topLevel) + public static void WriteTypeNameTo(TextWriter writer, IProtobufType type, TopLevel topLevel) { if (type is TopLevel { Parent: not null } typeTopLevel && typeTopLevel.Parent != topLevel) { diff --git a/src/LibProtodec/Models/TopLevels/Enum.cs b/src/LibProtodec/Models/Protobuf/TopLevels/Enum.cs similarity index 88% rename from src/LibProtodec/Models/TopLevels/Enum.cs rename to src/LibProtodec/Models/Protobuf/TopLevels/Enum.cs index b1c1404..dc8b84e 100644 --- a/src/LibProtodec/Models/TopLevels/Enum.cs +++ b/src/LibProtodec/Models/Protobuf/TopLevels/Enum.cs @@ -4,13 +4,13 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -global using Enum = LibProtodec.Models.TopLevels.Enum; +global using Enum = LibProtodec.Models.Protobuf.TopLevels.Enum; using System.CodeDom.Compiler; using System.Collections.Generic; -using LibProtodec.Models.Fields; -using LibProtodec.Models.Types; +using LibProtodec.Models.Protobuf.Fields; +using LibProtodec.Models.Protobuf.Types; -namespace LibProtodec.Models.TopLevels; +namespace LibProtodec.Models.Protobuf.TopLevels; public sealed class Enum : TopLevel, INestableType { diff --git a/src/LibProtodec/Models/TopLevels/Message.cs b/src/LibProtodec/Models/Protobuf/TopLevels/Message.cs similarity index 87% rename from src/LibProtodec/Models/TopLevels/Message.cs rename to src/LibProtodec/Models/Protobuf/TopLevels/Message.cs index ae8430f..a2081b2 100644 --- a/src/LibProtodec/Models/TopLevels/Message.cs +++ b/src/LibProtodec/Models/Protobuf/TopLevels/Message.cs @@ -7,14 +7,14 @@ using System.CodeDom.Compiler; using System.Collections.Generic; using System.Linq; -using LibProtodec.Models.Fields; -using LibProtodec.Models.Types; +using LibProtodec.Models.Protobuf.Fields; +using LibProtodec.Models.Protobuf.Types; -namespace LibProtodec.Models.TopLevels; +namespace LibProtodec.Models.Protobuf.TopLevels; public sealed class Message : TopLevel, INestableType { - public readonly Dictionary OneOfs = []; + public readonly Dictionary> OneOfs = []; public readonly Dictionary Fields = []; public readonly Dictionary Nested = []; @@ -40,7 +40,7 @@ public sealed class Message : TopLevel, INestableType field.WriteTo(writer, this, isOneOf: false); } - foreach ((string name, int[] fieldIds) in OneOfs) + foreach ((string name, List fieldIds) in OneOfs) { // ReSharper disable once StringLiteralTypo writer.Write("oneof "); diff --git a/src/LibProtodec/Models/TopLevels/Service.cs b/src/LibProtodec/Models/Protobuf/TopLevels/Service.cs similarity index 90% rename from src/LibProtodec/Models/TopLevels/Service.cs rename to src/LibProtodec/Models/Protobuf/TopLevels/Service.cs index fc46c5a..6f42874 100644 --- a/src/LibProtodec/Models/TopLevels/Service.cs +++ b/src/LibProtodec/Models/Protobuf/TopLevels/Service.cs @@ -6,9 +6,9 @@ using System.CodeDom.Compiler; using System.Collections.Generic; -using LibProtodec.Models.Fields; +using LibProtodec.Models.Protobuf.Fields; -namespace LibProtodec.Models.TopLevels; +namespace LibProtodec.Models.Protobuf.TopLevels; public sealed class Service : TopLevel { diff --git a/src/LibProtodec/Models/TopLevels/TopLevel.cs b/src/LibProtodec/Models/Protobuf/TopLevels/TopLevel.cs similarity index 95% rename from src/LibProtodec/Models/TopLevels/TopLevel.cs rename to src/LibProtodec/Models/Protobuf/TopLevels/TopLevel.cs index f3815d5..4bab6ed 100644 --- a/src/LibProtodec/Models/TopLevels/TopLevel.cs +++ b/src/LibProtodec/Models/Protobuf/TopLevels/TopLevel.cs @@ -7,7 +7,7 @@ using System.CodeDom.Compiler; using System.Collections.Generic; -namespace LibProtodec.Models.TopLevels; +namespace LibProtodec.Models.Protobuf.TopLevels; public abstract class TopLevel { diff --git a/src/LibProtodec/Models/Types/INestableType.cs b/src/LibProtodec/Models/Protobuf/Types/INestableType.cs similarity index 78% rename from src/LibProtodec/Models/Types/INestableType.cs rename to src/LibProtodec/Models/Protobuf/Types/INestableType.cs index 8977a3f..6bc1879 100644 --- a/src/LibProtodec/Models/Types/INestableType.cs +++ b/src/LibProtodec/Models/Protobuf/Types/INestableType.cs @@ -4,9 +4,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -namespace LibProtodec.Models.Types; +namespace LibProtodec.Models.Protobuf.Types; -public interface INestableType : IType +public interface INestableType : IProtobufType { Protobuf? Protobuf { get; } diff --git a/src/LibProtodec/Models/Types/IType.cs b/src/LibProtodec/Models/Protobuf/Types/IProtobufType.cs similarity index 69% rename from src/LibProtodec/Models/Types/IType.cs rename to src/LibProtodec/Models/Protobuf/Types/IProtobufType.cs index 7a180ac..a272204 100644 --- a/src/LibProtodec/Models/Types/IType.cs +++ b/src/LibProtodec/Models/Protobuf/Types/IProtobufType.cs @@ -4,14 +4,14 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -namespace LibProtodec.Models.Types; +namespace LibProtodec.Models.Protobuf.Types; -public interface IType +public interface IProtobufType { string Name { get; } } -public sealed class External(string typeName) : IType +public sealed class External(string typeName) : IProtobufType { public string Name => typeName; diff --git a/src/LibProtodec/Models/Types/Map.cs b/src/LibProtodec/Models/Protobuf/Types/Map.cs similarity index 70% rename from src/LibProtodec/Models/Types/Map.cs rename to src/LibProtodec/Models/Protobuf/Types/Map.cs index ba6b31d..f22f30a 100644 --- a/src/LibProtodec/Models/Types/Map.cs +++ b/src/LibProtodec/Models/Protobuf/Types/Map.cs @@ -4,9 +4,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -namespace LibProtodec.Models.Types; +namespace LibProtodec.Models.Protobuf.Types; -public sealed class Map(IType typeKey, IType typeVal) : IType +public sealed class Map(IProtobufType typeKey, IProtobufType typeVal) : IProtobufType { public string Name => $"map<{typeKey.Name}, {typeVal.Name}>"; diff --git a/src/LibProtodec/Models/Types/Repeated.cs b/src/LibProtodec/Models/Protobuf/Types/Repeated.cs similarity index 73% rename from src/LibProtodec/Models/Types/Repeated.cs rename to src/LibProtodec/Models/Protobuf/Types/Repeated.cs index ac02576..5bdb618 100644 --- a/src/LibProtodec/Models/Types/Repeated.cs +++ b/src/LibProtodec/Models/Protobuf/Types/Repeated.cs @@ -4,9 +4,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -namespace LibProtodec.Models.Types; +namespace LibProtodec.Models.Protobuf.Types; -public sealed class Repeated(IType type) : IType +public sealed class Repeated(IProtobufType type) : IProtobufType { public string Name => $"repeated {type.Name}"; diff --git a/src/LibProtodec/Models/Protobuf/Types/Scalar.cs b/src/LibProtodec/Models/Protobuf/Types/Scalar.cs new file mode 100644 index 0000000..2feec68 --- /dev/null +++ b/src/LibProtodec/Models/Protobuf/Types/Scalar.cs @@ -0,0 +1,27 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +namespace LibProtodec.Models.Protobuf.Types; + +// ReSharper disable StringLiteralTypo +public static class Scalar +{ + public static readonly IProtobufType Bool = new External("bool"); + public static readonly IProtobufType Bytes = new External("bytes"); + public static readonly IProtobufType Double = new External("double"); + public static readonly IProtobufType Fixed32 = new External("fixed32"); + public static readonly IProtobufType Fixed64 = new External("fixed64"); + public static readonly IProtobufType Float = new External("float"); + public static readonly IProtobufType Int32 = new External("int32"); + public static readonly IProtobufType Int64 = new External("int64"); + public static readonly IProtobufType SFixed32 = new External("sfixed32"); + public static readonly IProtobufType SFixed64 = new External("sfixed64"); + public static readonly IProtobufType SInt32 = new External("sint32"); + public static readonly IProtobufType SInt64 = new External("sint64"); + public static readonly IProtobufType String = new External("string"); + public static readonly IProtobufType UInt32 = new External("uint32"); + public static readonly IProtobufType UInt64 = new External("uint64"); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Protobuf/Types/WellKnown.cs b/src/LibProtodec/Models/Protobuf/Types/WellKnown.cs new file mode 100644 index 0000000..488ac4a --- /dev/null +++ b/src/LibProtodec/Models/Protobuf/Types/WellKnown.cs @@ -0,0 +1,39 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +namespace LibProtodec.Models.Protobuf.Types; + +public static class WellKnown +{ + public static readonly IProtobufType Any = new External("google.protobuf.Any"); + public static readonly IProtobufType Api = new External("google.protobuf.Api"); + public static readonly IProtobufType BoolValue = new External("google.protobuf.BoolValue"); + public static readonly IProtobufType BytesValue = new External("google.protobuf.BytesValue"); + public static readonly IProtobufType DoubleValue = new External("google.protobuf.DoubleValue"); + public static readonly IProtobufType Duration = new External("google.protobuf.Duration"); + public static readonly IProtobufType Empty = new External("google.protobuf.Empty"); + public static readonly IProtobufType Enum = new External("google.protobuf.Enum"); + public static readonly IProtobufType EnumValue = new External("google.protobuf.EnumValue"); + public static readonly IProtobufType Field = new External("google.protobuf.Field"); + public static readonly IProtobufType FieldMask = new External("google.protobuf.FieldMask"); + public static readonly IProtobufType FloatValue = new External("google.protobuf.FloatValue"); + public static readonly IProtobufType Int32Value = new External("google.protobuf.Int32Value"); + public static readonly IProtobufType Int64Value = new External("google.protobuf.Int64Value"); + public static readonly IProtobufType ListValue = new External("google.protobuf.ListValue"); + public static readonly IProtobufType Method = new External("google.protobuf.Method"); + public static readonly IProtobufType Mixin = new External("google.protobuf.Mixin"); + public static readonly IProtobufType NullValue = new External("google.protobuf.NullValue"); + public static readonly IProtobufType Option = new External("google.protobuf.Option"); + public static readonly IProtobufType SourceContext = new External("google.protobuf.SourceContext"); + public static readonly IProtobufType StringValue = new External("google.protobuf.StringValue"); + public static readonly IProtobufType Struct = new External("google.protobuf.Struct"); + public static readonly IProtobufType Syntax = new External("google.protobuf.Syntax"); + public static readonly IProtobufType Timestamp = new External("google.protobuf.Timestamp"); + public static readonly IProtobufType Type = new External("google.protobuf.Type"); + public static readonly IProtobufType UInt32Value = new External("google.protobuf.UInt32Value"); + public static readonly IProtobufType UInt64Value = new External("google.protobuf.UInt64Value"); + public static readonly IProtobufType Value = new External("google.protobuf.Value"); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Types/Scalar.cs b/src/LibProtodec/Models/Types/Scalar.cs deleted file mode 100644 index 510ec94..0000000 --- a/src/LibProtodec/Models/Types/Scalar.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright © 2024 Xpl0itR -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -namespace LibProtodec.Models.Types; - -// ReSharper disable StringLiteralTypo -public static class Scalar -{ - public static readonly IType Bool = new External("bool"); - public static readonly IType Bytes = new External("bytes"); - public static readonly IType Double = new External("double"); - public static readonly IType Fixed32 = new External("fixed32"); - public static readonly IType Fixed64 = new External("fixed64"); - public static readonly IType Float = new External("float"); - public static readonly IType Int32 = new External("int32"); - public static readonly IType Int64 = new External("int64"); - public static readonly IType SFixed32 = new External("sfixed32"); - public static readonly IType SFixed64 = new External("sfixed64"); - public static readonly IType SInt32 = new External("sint32"); - public static readonly IType SInt64 = new External("sint64"); - public static readonly IType String = new External("string"); - public static readonly IType UInt32 = new External("uint32"); - public static readonly IType UInt64 = new External("uint64"); -} \ No newline at end of file diff --git a/src/LibProtodec/Models/Types/WellKnown.cs b/src/LibProtodec/Models/Types/WellKnown.cs deleted file mode 100644 index 4e4e8c9..0000000 --- a/src/LibProtodec/Models/Types/WellKnown.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2024 Xpl0itR -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -namespace LibProtodec.Models.Types; - -public static class WellKnown -{ - public static readonly IType Any = new External("google.protobuf.Any"); - public static readonly IType Api = new External("google.protobuf.Api"); - public static readonly IType BoolValue = new External("google.protobuf.BoolValue"); - public static readonly IType BytesValue = new External("google.protobuf.BytesValue"); - public static readonly IType DoubleValue = new External("google.protobuf.DoubleValue"); - public static readonly IType Duration = new External("google.protobuf.Duration"); - public static readonly IType Empty = new External("google.protobuf.Empty"); - public static readonly IType Enum = new External("google.protobuf.Enum"); - public static readonly IType EnumValue = new External("google.protobuf.EnumValue"); - public static readonly IType Field = new External("google.protobuf.Field"); - public static readonly IType FieldMask = new External("google.protobuf.FieldMask"); - public static readonly IType FloatValue = new External("google.protobuf.FloatValue"); - public static readonly IType Int32Value = new External("google.protobuf.Int32Value"); - public static readonly IType Int64Value = new External("google.protobuf.Int64Value"); - public static readonly IType ListValue = new External("google.protobuf.ListValue"); - public static readonly IType Method = new External("google.protobuf.Method"); - public static readonly IType Mixin = new External("google.protobuf.Mixin"); - public static readonly IType NullValue = new External("google.protobuf.NullValue"); - public static readonly IType Option = new External("google.protobuf.Option"); - public static readonly IType SourceContext = new External("google.protobuf.SourceContext"); - public static readonly IType StringValue = new External("google.protobuf.StringValue"); - public static readonly IType Struct = new External("google.protobuf.Struct"); - public static readonly IType Syntax = new External("google.protobuf.Syntax"); - public static readonly IType Timestamp = new External("google.protobuf.Timestamp"); - public static readonly IType Type = new External("google.protobuf.Type"); - public static readonly IType UInt32Value = new External("google.protobuf.UInt32Value"); - public static readonly IType UInt64Value = new External("google.protobuf.UInt64Value"); - public static readonly IType Value = new External("google.protobuf.Value"); -} \ No newline at end of file diff --git a/src/LibProtodec/ProtodecContext.cs b/src/LibProtodec/ProtodecContext.cs index 23c83f0..a6939d5 100644 --- a/src/LibProtodec/ProtodecContext.cs +++ b/src/LibProtodec/ProtodecContext.cs @@ -10,24 +10,21 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reflection; using SystemEx; using CommunityToolkit.Diagnostics; -using LibProtodec.Models; -using LibProtodec.Models.Fields; -using LibProtodec.Models.TopLevels; -using LibProtodec.Models.Types; +using LibProtodec.Models.Cil; +using LibProtodec.Models.Protobuf; +using LibProtodec.Models.Protobuf.Fields; +using LibProtodec.Models.Protobuf.TopLevels; +using LibProtodec.Models.Protobuf.Types; namespace LibProtodec; -public delegate bool TypeLookupFunc(Type type, [NotNullWhen(true)] out IType? fieldType, out string? import); +public delegate bool TypeLookupFunc(ICilType cilType, [NotNullWhen(true)] out IProtobufType? protobufType, out string? import); public delegate bool NameLookupFunc(string name, [MaybeNullWhen(false)] out string translatedName); public sealed class ProtodecContext { - private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; - private const BindingFlags PublicInstanceDeclared = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; - private readonly Dictionary _parsed = []; public readonly List Protobufs = []; @@ -52,11 +49,11 @@ public sealed class ProtodecContext } } - public Message ParseMessage(Type messageClass, ParserOptions options = ParserOptions.None) + public Message ParseMessage(ICilType messageClass, ParserOptions options = ParserOptions.None) { Guard.IsTrue(messageClass is { IsClass: true, IsSealed: true }); - if (_parsed.TryGetValue(messageClass.FullName ?? messageClass.Name, out TopLevel? parsedMessage)) + if (_parsed.TryGetValue(messageClass.FullName, out TopLevel? parsedMessage)) { return (Message)parsedMessage; } @@ -64,50 +61,48 @@ public sealed class ProtodecContext Message message = new() { Name = TranslateTypeName(messageClass), - IsObsolete = HasObsoleteAttribute(messageClass.GetCustomAttributesData()) + IsObsolete = HasObsoleteAttribute(messageClass.GetCustomAttributes()) }; - _parsed.Add(messageClass.FullName ?? messageClass.Name, message); + _parsed.Add(messageClass.FullName, message); Protobuf protobuf = GetProtobuf(messageClass, message, options); - FieldInfo[] idFields = messageClass.GetFields(PublicStatic); - PropertyInfo[] properties = messageClass.GetProperties(PublicInstanceDeclared); + List idFields = messageClass.GetFields() + .Where(static field => field is { IsPublic: true, IsStatic: true, IsLiteral: true }) + .ToList(); - for (int pi = 0, fi = 0; pi < properties.Length; pi++, fi++) + List properties = messageClass.GetProperties() + .Where(static property => property is { IsInherited: false, CanRead: true, Getter: { IsPublic: true, IsStatic: false, IsVirtual: false } }) + .ToList(); + + for (int pi = 0, fi = 0; pi < properties.Count; pi++) { - PropertyInfo property = properties[pi]; - IList attributes = property.GetCustomAttributesData(); + ICilProperty property = properties[pi]; + List attributes = property.GetCustomAttributes().ToList(); - if (((options & ParserOptions.IncludePropertiesWithoutNonUserCodeAttribute) == 0 && !HasNonUserCodeAttribute(attributes)) - || property.GetMethod?.IsVirtual != false) + if (((options & ParserOptions.IncludePropertiesWithoutNonUserCodeAttribute) == 0 && !HasNonUserCodeAttribute(attributes))) { - fi--; continue; } - Type propertyType = property.PropertyType; + ICilType propertyType = property.Type; // only OneOf enums are defined nested directly in the message class if (propertyType.IsEnum && propertyType.DeclaringType?.Name == messageClass.Name) { string oneOfName = TranslateOneOfPropName(property.Name); - int[] oneOfProtoFieldIds = propertyType.GetFields(PublicStatic) - .Select(static field => (int)field.GetRawConstantValue()!) - .Where(static id => id > 0) - .ToArray(); + List oneOfProtoFieldIds = propertyType.GetFields() + .Where(static field => field.IsLiteral) + .Select(static field => (int)field.ConstantValue!) + .Where(static id => id > 0) + .ToList(); message.OneOfs.Add(oneOfName, oneOfProtoFieldIds); - - fi--; continue; } - FieldInfo idField = idFields[fi]; - Guard.IsTrue(idField.IsLiteral); - Guard.IsEqualTo(idField.FieldType.Name, nameof(Int32)); - bool msgFieldHasHasProp = false; // some field properties are immediately followed by an additional "Has" get-only boolean property - if (properties.Length > pi + 1 && properties[pi + 1].PropertyType.Name == nameof(Boolean) && !properties[pi + 1].CanWrite) + if (properties.Count > pi + 1 && properties[pi + 1].Type.Name == nameof(Boolean) && !properties[pi + 1].CanWrite) { msgFieldHasHasProp = true; pi++; @@ -117,22 +112,23 @@ public sealed class ProtodecContext { Type = ParseFieldType(propertyType, options, protobuf), Name = TranslateMessageFieldName(property.Name), - Id = (int)idField.GetRawConstantValue()!, + Id = (int)idFields[fi].ConstantValue!, IsObsolete = HasObsoleteAttribute(attributes), HasHasProp = msgFieldHasHasProp }; message.Fields.Add(field.Id, field); + fi++; } return message; } - public Enum ParseEnum(Type enumEnum, ParserOptions options = ParserOptions.None) + public Enum ParseEnum(ICilType enumEnum, ParserOptions options = ParserOptions.None) { Guard.IsTrue(enumEnum.IsEnum); - if (_parsed.TryGetValue(enumEnum.FullName ?? enumEnum.Name, out TopLevel? parsedEnum)) + if (_parsed.TryGetValue(enumEnum.FullName, out TopLevel? parsedEnum)) { return (Enum)parsedEnum; } @@ -140,20 +136,22 @@ public sealed class ProtodecContext Enum @enum = new() { Name = TranslateTypeName(enumEnum), - IsObsolete = HasObsoleteAttribute(enumEnum.GetCustomAttributesData()) + IsObsolete = HasObsoleteAttribute(enumEnum.GetCustomAttributes()) }; - _parsed.Add(enumEnum.FullName ?? enumEnum.Name, @enum); + _parsed.Add(enumEnum.FullName, @enum); Protobuf protobuf = GetProtobuf(enumEnum, @enum, options); - foreach (FieldInfo field in enumEnum.GetFields(PublicStatic)) + foreach (ICilField field in enumEnum.GetFields().Where(static field => field.IsLiteral)) { + List attributes = field.GetCustomAttributes().ToList(); + @enum.Fields.Add( new EnumField { - Id = (int)field.GetRawConstantValue()!, - Name = TranslateEnumFieldName(field, @enum.Name), - IsObsolete = HasObsoleteAttribute(field.GetCustomAttributesData()) + Id = (int)field.ConstantValue!, + Name = TranslateEnumFieldName(attributes, field.Name, @enum.Name), + IsObsolete = HasObsoleteAttribute(attributes) }); } @@ -166,7 +164,7 @@ public sealed class ProtodecContext return @enum; } - public Service ParseService(Type serviceClass, ParserOptions options = ParserOptions.None) + public Service ParseService(ICilType serviceClass, ParserOptions options = ParserOptions.None) { Guard.IsTrue(serviceClass.IsClass); @@ -175,9 +173,9 @@ public sealed class ProtodecContext { if (serviceClass is { IsSealed: true, IsNested: false }) { - Type[] nested = serviceClass.GetNestedTypes(); - serviceClass = nested.SingleOrDefault(static nested => nested is { IsAbstract: true, IsSealed: false }) - ?? nested.Single(static nested => nested is { IsClass: true, IsAbstract: false }); + List nested = serviceClass.GetNestedTypes().ToList(); + serviceClass = nested.SingleOrDefault(static nested => nested is { IsAbstract: true, IsSealed: false }) + ?? nested.Single(static nested => nested is { IsClass: true, IsAbstract: false }); } if (serviceClass is { IsNested: true, IsAbstract: true, IsSealed: false }) @@ -193,7 +191,7 @@ public sealed class ProtodecContext Guard.IsNotNull(isClientClass); - if (_parsed.TryGetValue(serviceClass.DeclaringType!.FullName ?? serviceClass.DeclaringType!.Name, out TopLevel? parsedService)) + if (_parsed.TryGetValue(serviceClass.DeclaringType!.FullName, out TopLevel? parsedService)) { return (Service)parsedService; } @@ -201,23 +199,23 @@ public sealed class ProtodecContext Service service = new() { Name = TranslateTypeName(serviceClass.DeclaringType), - IsObsolete = HasObsoleteAttribute(serviceClass.GetCustomAttributesData()) + IsObsolete = HasObsoleteAttribute(serviceClass.GetCustomAttributes()) }; - _parsed.Add(serviceClass.DeclaringType!.FullName ?? serviceClass.DeclaringType.Name, service); + _parsed.Add(serviceClass.DeclaringType!.FullName, service); Protobuf protobuf = NewProtobuf(serviceClass, service); - foreach (MethodInfo method in serviceClass.GetMethods(PublicInstanceDeclared)) + foreach (ICilMethod method in serviceClass.GetMethods().Where(static method => method is { IsInherited: false, IsPublic: true, IsStatic: false })) { - IList attributes = method.GetCustomAttributesData(); + List attributes = method.GetCustomAttributes().ToList(); if ((options & ParserOptions.IncludeServiceMethodsWithoutGeneratedCodeAttribute) == 0 && !HasGeneratedCodeAttribute(attributes, "grpc_csharp_plugin")) { continue; } - Type requestType, responseType, returnType = method.ReturnType; - bool streamReq, streamRes; + ICilType requestType, responseType, returnType = method.ReturnType; + bool streamReq, streamRes; if (isClientClass.Value) { @@ -227,14 +225,13 @@ public sealed class ProtodecContext continue; } - ParameterInfo[] parameters = method.GetParameters(); - if (parameters.Length > 2) + List parameters = method.GetParameterTypes().ToList(); + if (parameters.Count > 2) { continue; } - Type firstParamType = parameters[0].ParameterType; - switch (returnType.GenericTypeArguments.Length) + switch (returnType.GenericTypeArguments.Count) { case 2: requestType = returnType.GenericTypeArguments[0]; @@ -243,13 +240,13 @@ public sealed class ProtodecContext streamRes = returnTypeName == "AsyncDuplexStreamingCall`2"; break; case 1: - requestType = firstParamType; + requestType = parameters[0]; responseType = returnType.GenericTypeArguments[0]; streamReq = false; streamRes = true; break; default: - requestType = firstParamType; + requestType = parameters[0]; responseType = returnType; streamReq = false; streamRes = false; @@ -258,21 +255,20 @@ public sealed class ProtodecContext } else { - ParameterInfo[] parameters = method.GetParameters(); - Type firstParamType = parameters[0].ParameterType; + List parameters = method.GetParameterTypes().ToList(); - if (firstParamType.GenericTypeArguments.Length == 1) + if (parameters[0].GenericTypeArguments.Count == 1) { streamReq = true; - requestType = firstParamType.GenericTypeArguments[0]; + requestType = parameters[0].GenericTypeArguments[0]; } else { streamReq = false; - requestType = firstParamType; + requestType = parameters[0]; } - if (returnType.GenericTypeArguments.Length == 1) + if (returnType.GenericTypeArguments.Count == 1) { streamRes = false; responseType = returnType.GenericTypeArguments[0]; @@ -280,7 +276,7 @@ public sealed class ProtodecContext else { streamRes = true; - responseType = parameters[1].ParameterType.GenericTypeArguments[0]; + responseType = parameters[1].GenericTypeArguments[0]; } } @@ -299,9 +295,9 @@ public sealed class ProtodecContext return service; } - private IType ParseFieldType(Type type, ParserOptions options, Protobuf referencingProtobuf) + private IProtobufType ParseFieldType(ICilType type, ParserOptions options, Protobuf referencingProtobuf) { - switch (type.GenericTypeArguments.Length) + switch (type.GenericTypeArguments.Count) { case 1: return new Repeated( @@ -312,7 +308,7 @@ public sealed class ProtodecContext ParseFieldType(type.GenericTypeArguments[1], options, referencingProtobuf)); } - if (TypeLookup(type, out IType? fieldType, out string? import)) + if (TypeLookup(type, out IProtobufType? fieldType, out string? import)) { if (import is not null) { @@ -345,11 +341,11 @@ public sealed class ProtodecContext return fieldType; } - private Protobuf NewProtobuf(Type topLevelType, TopLevel topLevel) + private Protobuf NewProtobuf(ICilType topLevelType, TopLevel topLevel) { Protobuf protobuf = new() { - AssemblyName = topLevelType.Assembly.FullName, + AssemblyName = topLevelType.DeclaringAssemblyName, Namespace = topLevelType.Namespace }; @@ -360,14 +356,14 @@ public sealed class ProtodecContext return protobuf; } - private Protobuf GetProtobuf(Type topLevelType, T topLevel, ParserOptions options) + private Protobuf GetProtobuf(ICilType topLevelType, T topLevel, ParserOptions options) where T : TopLevel, INestableType { Protobuf protobuf; if (topLevelType.IsNested) { - Type parent = topLevelType.DeclaringType!.DeclaringType!; - if (!_parsed.TryGetValue(parent.FullName ?? parent.Name, out TopLevel? parentTopLevel)) + ICilType parent = topLevelType.DeclaringType!.DeclaringType!; + if (!_parsed.TryGetValue(parent.FullName, out TopLevel? parentTopLevel)) { parentTopLevel = ParseMessage(parent, options); } @@ -421,25 +417,22 @@ public sealed class ProtodecContext return translatedName!.ToSnakeCaseLower(); } - private string TranslateEnumFieldName(FieldInfo field, string enumName) + private string TranslateEnumFieldName(IEnumerable attributes, string fieldName, string enumName) { - if (field.GetCustomAttributesData() - .SingleOrDefault(static attr => attr.AttributeType.Name == "OriginalNameAttribute") - ?.ConstructorArguments[0] - .Value - is string originalName) + if (attributes.SingleOrDefault(static attr => attr.Type.Name == "OriginalNameAttribute") + ?.ConstructorArguments[0] is string originalName) { return originalName; } - if (NameLookup?.Invoke(field.Name, out string? fieldName) != true) + if (NameLookup?.Invoke(fieldName, out string? translatedName) == true) { - fieldName = field.Name; + fieldName = translatedName; } - if (!IsBeebyted(fieldName!)) + if (!IsBeebyted(fieldName)) { - fieldName = fieldName!.ToSnakeCaseUpper(); + fieldName = fieldName.ToSnakeCaseUpper(); } if (!IsBeebyted(enumName)) @@ -450,14 +443,12 @@ public sealed class ProtodecContext return enumName + '_' + fieldName; } - private string TranslateTypeName(Type type) + private string TranslateTypeName(ICilType type) { if (NameLookup is null) return type.Name; - string? fullName = type.FullName; - Guard.IsNotNull(fullName); - + string fullName = type.FullName; int genericArgs = fullName.IndexOf('['); if (genericArgs != -1) fullName = fullName[..genericArgs]; @@ -478,162 +469,162 @@ public sealed class ProtodecContext return translatedName; } - public static bool LookupScalarAndWellKnownTypes(Type type, [NotNullWhen(true)] out IType? fieldType, out string? import) + public static bool LookupScalarAndWellKnownTypes(ICilType cilType, [NotNullWhen(true)] out IProtobufType? protobufType, out string? import) { - switch (type.FullName) + switch (cilType.FullName) { case "System.String": - import = null; - fieldType = Scalar.String; + import = null; + protobufType = Scalar.String; return true; case "System.Boolean": - import = null; - fieldType = Scalar.Bool; + import = null; + protobufType = Scalar.Bool; return true; case "System.Double": - import = null; - fieldType = Scalar.Double; + import = null; + protobufType = Scalar.Double; return true; case "System.UInt32": - import = null; - fieldType = Scalar.UInt32; + import = null; + protobufType = Scalar.UInt32; return true; case "System.UInt64": - import = null; - fieldType = Scalar.UInt64; + import = null; + protobufType = Scalar.UInt64; return true; case "System.Int32": - import = null; - fieldType = Scalar.Int32; + import = null; + protobufType = Scalar.Int32; return true; case "System.Int64": - import = null; - fieldType = Scalar.Int64; + import = null; + protobufType = Scalar.Int64; return true; case "System.Single": - import = null; - fieldType = Scalar.Float; + import = null; + protobufType = Scalar.Float; return true; case "Google.Protobuf.ByteString": - import = null; - fieldType = Scalar.Bytes; + import = null; + protobufType = Scalar.Bytes; return true; case "Google.Protobuf.WellKnownTypes.Any": - import = "google/protobuf/any.proto"; - fieldType = WellKnown.Any; + import = "google/protobuf/any.proto"; + protobufType = WellKnown.Any; return true; case "Google.Protobuf.WellKnownTypes.Api": - import = "google/protobuf/api.proto"; - fieldType = WellKnown.Api; + import = "google/protobuf/api.proto"; + protobufType = WellKnown.Api; return true; case "Google.Protobuf.WellKnownTypes.BoolValue": - import = "google/protobuf/wrappers.proto"; - fieldType = WellKnown.BoolValue; + import = "google/protobuf/wrappers.proto"; + protobufType = WellKnown.BoolValue; return true; case "Google.Protobuf.WellKnownTypes.BytesValue": - import = "google/protobuf/wrappers.proto"; - fieldType = WellKnown.BytesValue; + import = "google/protobuf/wrappers.proto"; + protobufType = WellKnown.BytesValue; return true; case "Google.Protobuf.WellKnownTypes.DoubleValue": - import = "google/protobuf/wrappers.proto"; - fieldType = WellKnown.DoubleValue; + import = "google/protobuf/wrappers.proto"; + protobufType = WellKnown.DoubleValue; return true; case "Google.Protobuf.WellKnownTypes.Duration": - import = "google/protobuf/duration.proto"; - fieldType = WellKnown.Duration; + import = "google/protobuf/duration.proto"; + protobufType = WellKnown.Duration; return true; case "Google.Protobuf.WellKnownTypes.Empty": - import = "google/protobuf/empty.proto"; - fieldType = WellKnown.Empty; + import = "google/protobuf/empty.proto"; + protobufType = WellKnown.Empty; return true; case "Google.Protobuf.WellKnownTypes.Enum": - import = "google/protobuf/type.proto"; - fieldType = WellKnown.Enum; + import = "google/protobuf/type.proto"; + protobufType = WellKnown.Enum; return true; case "Google.Protobuf.WellKnownTypes.EnumValue": - import = "google/protobuf/type.proto"; - fieldType = WellKnown.EnumValue; + import = "google/protobuf/type.proto"; + protobufType = WellKnown.EnumValue; return true; case "Google.Protobuf.WellKnownTypes.Field": - import = "google/protobuf/type.proto"; - fieldType = WellKnown.Field; + import = "google/protobuf/type.proto"; + protobufType = WellKnown.Field; return true; case "Google.Protobuf.WellKnownTypes.FieldMask": - import = "google/protobuf/field_mask.proto"; - fieldType = WellKnown.FieldMask; + import = "google/protobuf/field_mask.proto"; + protobufType = WellKnown.FieldMask; return true; case "Google.Protobuf.WellKnownTypes.FloatValue": - import = "google/protobuf/wrappers.proto"; - fieldType = WellKnown.FloatValue; + import = "google/protobuf/wrappers.proto"; + protobufType = WellKnown.FloatValue; return true; case "Google.Protobuf.WellKnownTypes.Int32Value": - import = "google/protobuf/wrappers.proto"; - fieldType = WellKnown.Int32Value; + import = "google/protobuf/wrappers.proto"; + protobufType = WellKnown.Int32Value; return true; case "Google.Protobuf.WellKnownTypes.Int64Value": - import = "google/protobuf/wrappers.proto"; - fieldType = WellKnown.Int64Value; + import = "google/protobuf/wrappers.proto"; + protobufType = WellKnown.Int64Value; return true; case "Google.Protobuf.WellKnownTypes.ListValue": - import = "google/protobuf/struct.proto"; - fieldType = WellKnown.ListValue; + import = "google/protobuf/struct.proto"; + protobufType = WellKnown.ListValue; return true; case "Google.Protobuf.WellKnownTypes.Method": - import = "google/protobuf/api.proto"; - fieldType = WellKnown.Method; + import = "google/protobuf/api.proto"; + protobufType = WellKnown.Method; return true; case "Google.Protobuf.WellKnownTypes.Mixin": - import = "google/protobuf/api.proto"; - fieldType = WellKnown.Mixin; + import = "google/protobuf/api.proto"; + protobufType = WellKnown.Mixin; return true; case "Google.Protobuf.WellKnownTypes.NullValue": - import = "google/protobuf/struct.proto"; - fieldType = WellKnown.NullValue; + import = "google/protobuf/struct.proto"; + protobufType = WellKnown.NullValue; return true; case "Google.Protobuf.WellKnownTypes.Option": - import = "google/protobuf/type.proto"; - fieldType = WellKnown.Option; + import = "google/protobuf/type.proto"; + protobufType = WellKnown.Option; return true; case "Google.Protobuf.WellKnownTypes.SourceContext": - import = "google/protobuf/source_context.proto"; - fieldType = WellKnown.SourceContext; + import = "google/protobuf/source_context.proto"; + protobufType = WellKnown.SourceContext; return true; case "Google.Protobuf.WellKnownTypes.StringValue": - import = "google/protobuf/wrappers.proto"; - fieldType = WellKnown.StringValue; + import = "google/protobuf/wrappers.proto"; + protobufType = WellKnown.StringValue; return true; case "Google.Protobuf.WellKnownTypes.Struct": - import = "google/protobuf/struct.proto"; - fieldType = WellKnown.Struct; + import = "google/protobuf/struct.proto"; + protobufType = WellKnown.Struct; return true; case "Google.Protobuf.WellKnownTypes.Syntax": - import = "google/protobuf/type.proto"; - fieldType = WellKnown.Syntax; + import = "google/protobuf/type.proto"; + protobufType = WellKnown.Syntax; return true; case "Google.Protobuf.WellKnownTypes.Timestamp": - import = "google/protobuf/timestamp.proto"; - fieldType = WellKnown.Timestamp; + import = "google/protobuf/timestamp.proto"; + protobufType = WellKnown.Timestamp; return true; case "Google.Protobuf.WellKnownTypes.Type": - import = "google/protobuf/type.proto"; - fieldType = WellKnown.Type; + import = "google/protobuf/type.proto"; + protobufType = WellKnown.Type; return true; case "Google.Protobuf.WellKnownTypes.UInt32Value": - import = "google/protobuf/wrappers.proto"; - fieldType = WellKnown.UInt32Value; + import = "google/protobuf/wrappers.proto"; + protobufType = WellKnown.UInt32Value; return true; case "Google.Protobuf.WellKnownTypes.UInt64Value": - import = "google/protobuf/wrappers.proto"; - fieldType = WellKnown.UInt64Value; + import = "google/protobuf/wrappers.proto"; + protobufType = WellKnown.UInt64Value; return true; case "Google.Protobuf.WellKnownTypes.Value": - import = "google/protobuf/struct.proto"; - fieldType = WellKnown.Value; + import = "google/protobuf/struct.proto"; + protobufType = WellKnown.Value; return true; default: - import = null; - fieldType = null; + import = null; + protobufType = null; return false; } } @@ -642,13 +633,13 @@ public sealed class ProtodecContext private static bool IsBeebyted(string name) => name.Length == 11 && name.CountUpper() == 11; - private static bool HasGeneratedCodeAttribute(IEnumerable attributes, string tool) => - attributes.Any(attr => attr.AttributeType.Name == nameof(GeneratedCodeAttribute) - && attr.ConstructorArguments[0].Value as string == tool); + private static bool HasGeneratedCodeAttribute(IEnumerable attributes, string tool) => + attributes.Any(attr => attr.Type.Name == nameof(GeneratedCodeAttribute) + && attr.ConstructorArguments[0] as string == tool); - private static bool HasNonUserCodeAttribute(IEnumerable attributes) => - attributes.Any(static attr => attr.AttributeType.Name == nameof(DebuggerNonUserCodeAttribute)); + private static bool HasNonUserCodeAttribute(IEnumerable attributes) => + attributes.Any(static attr => attr.Type.Name == nameof(DebuggerNonUserCodeAttribute)); - private static bool HasObsoleteAttribute(IEnumerable attributes) => - attributes.Any(static attr => attr.AttributeType.Name == nameof(ObsoleteAttribute)); + private static bool HasObsoleteAttribute(IEnumerable attributes) => + attributes.Any(static attr => attr.Type.Name == nameof(ObsoleteAttribute)); } \ No newline at end of file diff --git a/src/protodec/Program.cs b/src/protodec/Program.cs index e8aad25..e99321b 100644 --- a/src/protodec/Program.cs +++ b/src/protodec/Program.cs @@ -1,10 +1,18 @@ -using System; +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Linq; using LibProtodec; -using LibProtodec.Models; +using LibProtodec.Loaders; +using LibProtodec.Models.Cil; +using LibProtodec.Models.Protobuf; const string indent = " "; const string help = """ @@ -39,17 +47,17 @@ if (args.Contains("--include_properties_without_non_user_code_attribute")) if (args.Contains("--include_service_methods_without_generated_code_attribute")) options |= ParserOptions.IncludeServiceMethodsWithoutGeneratedCodeAttribute; -using AssemblyInspector inspector = new(assembly); +using ICilAssemblyLoader loader = new ClrAssemblyLoader(assembly); ProtodecContext ctx = new(); -foreach (Type message in inspector.GetProtobufMessageTypes()) +foreach (ICilType message in GetProtobufMessageTypes()) { ctx.ParseMessage(message, options); } if (args.Contains("--parse_service_servers")) { - foreach (Type service in inspector.GetProtobufServiceServerTypes()) + foreach (ICilType service in GetProtobufServiceServerTypes()) { ctx.ParseService(service, options); } @@ -57,7 +65,7 @@ if (args.Contains("--parse_service_servers")) if (args.Contains("--parse_service_clients")) { - foreach (Type service in inspector.GetProtobufServiceClientTypes()) + foreach (ICilType service in GetProtobufServiceClientTypes()) { ctx.ParseService(service, options); } @@ -91,4 +99,20 @@ else using IndentedTextWriter indentWriter = new(streamWriter, indent); ctx.WriteAllTo(indentWriter); -} \ No newline at end of file +} + +IEnumerable GetProtobufMessageTypes() => + loader.LoadedTypes.Where( + type => type is { IsNested: false, IsSealed: true } + && type.Namespace?.StartsWith("Google.Protobuf", StringComparison.Ordinal) != true + && type.IsAssignableTo(loader.IMessage)); + +IEnumerable GetProtobufServiceClientTypes() => + loader.LoadedTypes.Where( + type => type is { IsNested: true, IsAbstract: false } + && type.IsAssignableTo(loader.ClientBase)); + +IEnumerable GetProtobufServiceServerTypes() => + loader.LoadedTypes.Where( + type => type is { IsNested: true, IsAbstract: true, DeclaringType: { IsNested: false, IsSealed: true, IsAbstract: true } } + && type.GetCustomAttributes().Any(attribute => attribute.Type == loader.BindServiceMethodAttribute)); \ No newline at end of file