diff --git a/.gitignore b/.gitignore
index d2845b1..9fbf6b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.vs
bin/
obj/
-*.csproj.user
\ No newline at end of file
+*.user
+.idea/
\ No newline at end of file
diff --git a/src/Publicizer/AssemblyEditor.cs b/src/Publicizer/AssemblyEditor.cs
index 4bbc802..8d9eba9 100644
--- a/src/Publicizer/AssemblyEditor.cs
+++ b/src/Publicizer/AssemblyEditor.cs
@@ -1,13 +1,46 @@
+using System;
+using System.Linq;
using dnlib.DotNet;
+using dnlib.DotNet.Emit;
+using FieldAttributes = dnlib.DotNet.FieldAttributes;
+using MethodAttributes = dnlib.DotNet.MethodAttributes;
+using TypeAttributes = dnlib.DotNet.TypeAttributes;
namespace Publicizer;
///
/// Class for making edits to assemblies and related types.
///
-internal static class AssemblyEditor
+internal class AssemblyEditor
{
- internal static bool PublicizeType(TypeDef type)
+ private readonly ModuleDef _module;
+ private readonly TypeDef? _accessAttributeType;
+ private readonly MethodDef? _accessAttributeConstructor;
+ internal AssemblyEditor(ModuleDef module, bool addOriginalAccessModifierAttribute)
+ {
+ _module = module;
+
+ // workaround for custom attributes not being supported in, at least some, core libs
+ bool isCoreLib = module.IsCoreLibraryModule ?? false;
+ if (!isCoreLib && addOriginalAccessModifierAttribute)
+ {
+ _accessAttributeType = GetOrCreateAccessAttributeType(module);
+ _accessAttributeConstructor = _accessAttributeType.FindConstructors().First();
+ }
+ }
+
+ internal bool IsAccessAttribute(ITypeDefOrRef typeRef)
+ {
+ return typeRef == _accessAttributeType;
+ }
+
+ internal bool publicizedAnyMemberInAssembly;
+ internal int publicizedTypesCount;
+ internal int publicizedPropertiesCount;
+ internal int publicizedMethodsCount;
+ internal int publicizedFieldsCount;
+
+ internal bool PublicizeType(TypeDef type)
{
TypeAttributes oldAttributes = type.Attributes;
type.Attributes &= ~TypeAttributes.VisibilityMask;
@@ -20,10 +53,17 @@ internal static bool PublicizeType(TypeDef type)
{
type.Attributes |= TypeAttributes.Public;
}
- return type.Attributes != oldAttributes;
+
+ if (type.Attributes != oldAttributes)
+ {
+ AddOriginalAccessModifierAttribute(type, ConvertAttributes(oldAttributes));
+ publicizedTypesCount++;
+ return true;
+ }
+ return false;
}
- internal static bool PublicizeProperty(PropertyDef property, bool includeVirtual = true)
+ internal bool PublicizeProperty(PropertyDef property, bool includeVirtual = true)
{
bool publicized = false;
@@ -37,26 +77,153 @@ internal static bool PublicizeProperty(PropertyDef property, bool includeVirtual
publicized |= PublicizeMethod(setMethod, includeVirtual);
}
+ if (publicized)
+ {
+ publicizedPropertiesCount++;
+ }
return publicized;
}
- internal static bool PublicizeMethod(MethodDef method, bool includeVirtual = true)
+ internal bool PublicizeMethod(MethodDef method, bool includeVirtual = true)
{
if (includeVirtual || !method.IsVirtual)
{
MethodAttributes oldAttributes = method.Attributes;
method.Attributes &= ~MethodAttributes.MemberAccessMask;
method.Attributes |= MethodAttributes.Public;
- return method.Attributes != oldAttributes;
+ if (method.Attributes != oldAttributes)
+ {
+ AddOriginalAccessModifierAttribute(method, ConvertAttributes(oldAttributes));
+ publicizedAnyMemberInAssembly = true;
+ publicizedMethodsCount++;
+ return true;
+ }
}
return false;
}
- internal static bool PublicizeField(FieldDef field)
+ internal bool PublicizeField(FieldDef field)
{
FieldAttributes oldAttributes = field.Attributes;
field.Attributes &= ~FieldAttributes.FieldAccessMask;
field.Attributes |= FieldAttributes.Public;
- return field.Attributes != oldAttributes;
+ if (field.Attributes != oldAttributes)
+ {
+ AddOriginalAccessModifierAttribute(field, ConvertAttributes(oldAttributes));
+ publicizedAnyMemberInAssembly = true;
+ publicizedFieldsCount++;
+ return true;
+ }
+ return false;
+ }
+
+ private void AddOriginalAccessModifierAttribute(IHasCustomAttribute item, AccessModifier original)
+ {
+ if (_accessAttributeConstructor == null)
+ {
+ return;
+ }
+ var attribute = new CustomAttribute(_accessAttributeConstructor);
+ var caArgument = new CAArgument(_module.CorLibTypes.String, AccessModifierToString(original));
+ attribute.ConstructorArguments.Add(caArgument);
+ item.CustomAttributes.Add(attribute);
+ }
+
+ private static readonly string s_accessAttributeNamespace = new UTF8String(nameof(Publicizer));
+ private static readonly string s_accessAttributeName = new UTF8String("OriginalAccessModifierAttribute");
+ private static TypeDef GetOrCreateAccessAttributeType(ModuleDef module)
+ {
+ TypeDef? attributeTypeDef = module.Types.FirstOrDefault(t => t.Namespace == s_accessAttributeNamespace && t.Name == s_accessAttributeName);
+ if (attributeTypeDef == null)
+ {
+ TypeRef attributeBaseTypeRef = module.CorLibTypes.GetTypeRef("System", "Attribute");
+ MemberRef attributeBaseConstructorMethodRef = new MemberRefUser(
+ module,
+ ".ctor",
+ MethodSig.CreateInstance(module.CorLibTypes.Void),
+ attributeBaseTypeRef
+ );
+ attributeTypeDef = new TypeDefUser(s_accessAttributeNamespace, s_accessAttributeName, attributeBaseTypeRef);
+ attributeTypeDef.Attributes = TypeAttributes.NestedAssembly | TypeAttributes.Sealed;
+
+ var methodSig = MethodSig.CreateInstance(
+ module.CorLibTypes.Void,
+ module.CorLibTypes.String
+ );
+ var methodDef = new MethodDefUser(
+ ".ctor",
+ methodSig,
+ MethodAttributes.Assembly | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.HideBySig
+ );
+
+ CilBody body = methodDef.Body = new CilBody();
+ body.Instructions.Add(new Instruction(OpCodes.Ldarg_0));
+ body.Instructions.Add(new Instruction(OpCodes.Call, attributeBaseConstructorMethodRef));
+ body.Instructions.Add(new Instruction(OpCodes.Ret));
+
+ attributeTypeDef.Methods.Add(methodDef);
+
+ module.Types.Add(attributeTypeDef);
+ }
+ return attributeTypeDef;
+ }
+
+ private static AccessModifier ConvertAttributes(TypeAttributes attributes)
+ {
+ attributes &= ~TypeAttributes.VisibilityMask;
+ return attributes switch
+ {
+ TypeAttributes.NotPublic => AccessModifier.Private,
+ TypeAttributes.Public => AccessModifier.Public,
+ TypeAttributes.NestedPublic => AccessModifier.Public,
+ TypeAttributes.NestedPrivate => AccessModifier.Private,
+ TypeAttributes.NestedFamily => AccessModifier.Protected,
+ TypeAttributes.NestedAssembly => AccessModifier.Internal,
+ TypeAttributes.NestedFamANDAssem => AccessModifier.PrivateProtected,
+ TypeAttributes.NestedFamORAssem => AccessModifier.ProtectedInternal,
+ _ => AccessModifier.File
+ };
+ }
+ private static AccessModifier ConvertAttributes(MethodAttributes attributes)
+ {
+ // methods and fields share the same visibility mask order and values
+ return ConvertAttributes((FieldAttributes)attributes);
+ }
+ private static AccessModifier ConvertAttributes(FieldAttributes attributes)
+ {
+ attributes &= ~FieldAttributes.FieldAccessMask;
+ return attributes switch
+ {
+ FieldAttributes.PrivateScope => AccessModifier.CompilerControlled,
+ FieldAttributes.Private => AccessModifier.Private,
+ FieldAttributes.FamANDAssem => AccessModifier.PrivateProtected,
+ FieldAttributes.Assembly => AccessModifier.Internal,
+ FieldAttributes.Family => AccessModifier.Protected,
+ FieldAttributes.FamORAssem => AccessModifier.ProtectedInternal,
+ FieldAttributes.Public => AccessModifier.Public,
+ _ => AccessModifier.File
+ };
+ }
+ // makes it more readable
+ // corresponds to official documentation
+ // https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers
+ private enum AccessModifier
+ {
+ Public, Private, Protected, Internal, ProtectedInternal, PrivateProtected, File, CompilerControlled
+ }
+ private static string AccessModifierToString(AccessModifier modifier)
+ {
+ return modifier switch
+ {
+ AccessModifier.Public => "public",
+ AccessModifier.Private => "private",
+ AccessModifier.Protected => "protected",
+ AccessModifier.Internal => "internal",
+ AccessModifier.ProtectedInternal => "protected internal",
+ AccessModifier.PrivateProtected => "private protected",
+ AccessModifier.File => "file",
+ AccessModifier.CompilerControlled => "[CompilerControlled|PrivateScope]",
+ _ => throw new ArgumentOutOfRangeException(nameof(modifier), modifier, null)
+ };
}
}
diff --git a/src/Publicizer/PublicizeAssemblies.cs b/src/Publicizer/PublicizeAssemblies.cs
index 06a58dc..8bc7f72 100644
--- a/src/Publicizer/PublicizeAssemblies.cs
+++ b/src/Publicizer/PublicizeAssemblies.cs
@@ -221,17 +221,20 @@ private static Dictionary GetPublicizerAssemb
private static bool PublicizeAssembly(ModuleDef module, PublicizerAssemblyContext assemblyContext, ITaskLogger logger)
{
- bool publicizedAnyMemberInAssembly = false;
var doNotPublicizePropertyMethods = new HashSet();
- int publicizedTypesCount = 0;
- int publicizedPropertiesCount = 0;
- int publicizedMethodsCount = 0;
- int publicizedFieldsCount = 0;
+ var assemblyEditor = new AssemblyEditor(module, assemblyContext.AddOriginalAccessModifierAttribute);
// TYPES
- foreach (TypeDef? typeDef in module.GetTypes())
+ foreach (TypeDef typeDef in module.GetTypes())
{
+ if (assemblyEditor.IsAccessAttribute(typeDef))
+ {
+ // do not publicize the access attribute itself, keep it internal
+ // also avoids the attribute attributing itself
+ continue;
+ }
+
doNotPublicizePropertyMethods.Clear();
bool publicizedAnyMemberInType = false;
@@ -262,11 +265,9 @@ private static bool PublicizeAssembly(ModuleDef module, PublicizerAssemblyContex
bool explicitlyPublicizeProperty = assemblyContext.PublicizeMemberPatterns.Contains(propertyName);
if (explicitlyPublicizeProperty)
{
- if (AssemblyEditor.PublicizeProperty(propertyDef))
+ if (assemblyEditor.PublicizeProperty(propertyDef))
{
publicizedAnyMemberInType = true;
- publicizedAnyMemberInAssembly = true;
- publicizedPropertiesCount++;
logger.Verbose($"Explicitly publicizing property: {propertyName}");
}
continue;
@@ -296,11 +297,9 @@ private static bool PublicizeAssembly(ModuleDef module, PublicizerAssemblyContex
continue;
}
- if (AssemblyEditor.PublicizeProperty(propertyDef, assemblyContext.IncludeVirtualMembers))
+ if (assemblyEditor.PublicizeProperty(propertyDef, assemblyContext.IncludeVirtualMembers))
{
publicizedAnyMemberInType = true;
- publicizedAnyMemberInAssembly = true;
- publicizedPropertiesCount++;
}
}
}
@@ -326,11 +325,9 @@ private static bool PublicizeAssembly(ModuleDef module, PublicizerAssemblyContex
bool explicitlyPublicizeMethod = assemblyContext.PublicizeMemberPatterns.Contains(methodName);
if (explicitlyPublicizeMethod)
{
- if (AssemblyEditor.PublicizeMethod(methodDef))
+ if (assemblyEditor.PublicizeMethod(methodDef))
{
publicizedAnyMemberInType = true;
- publicizedAnyMemberInAssembly = true;
- publicizedMethodsCount++;
logger.Verbose($"Explicitly publicizing method: {methodName}");
}
continue;
@@ -360,11 +357,9 @@ private static bool PublicizeAssembly(ModuleDef module, PublicizerAssemblyContex
continue;
}
- if (AssemblyEditor.PublicizeMethod(methodDef, assemblyContext.IncludeVirtualMembers))
+ if (assemblyEditor.PublicizeMethod(methodDef, assemblyContext.IncludeVirtualMembers))
{
publicizedAnyMemberInType = true;
- publicizedAnyMemberInAssembly = true;
- publicizedMethodsCount++;
}
}
}
@@ -384,11 +379,9 @@ private static bool PublicizeAssembly(ModuleDef module, PublicizerAssemblyContex
bool explicitlyPublicizeField = assemblyContext.PublicizeMemberPatterns.Contains(fieldName);
if (explicitlyPublicizeField)
{
- if (AssemblyEditor.PublicizeField(fieldDef))
+ if (assemblyEditor.PublicizeField(fieldDef))
{
publicizedAnyMemberInType = true;
- publicizedAnyMemberInAssembly = true;
- publicizedFieldsCount++;
logger.Verbose($"Explicitly publicizing field: {fieldName}");
}
continue;
@@ -418,22 +411,16 @@ private static bool PublicizeAssembly(ModuleDef module, PublicizerAssemblyContex
continue;
}
- if (AssemblyEditor.PublicizeField(fieldDef))
+ if (assemblyEditor.PublicizeField(fieldDef))
{
publicizedAnyMemberInType = true;
- publicizedAnyMemberInAssembly = true;
- publicizedFieldsCount++;
}
}
}
if (publicizedAnyMemberInType)
{
- if (AssemblyEditor.PublicizeType(typeDef))
- {
- publicizedAnyMemberInAssembly = true;
- publicizedTypesCount++;
- }
+ assemblyEditor.PublicizeType(typeDef);
continue;
}
@@ -446,10 +433,8 @@ private static bool PublicizeAssembly(ModuleDef module, PublicizerAssemblyContex
bool explicitlyPublicizeType = assemblyContext.PublicizeMemberPatterns.Contains(typeName);
if (explicitlyPublicizeType)
{
- if (AssemblyEditor.PublicizeType(typeDef))
+ if (assemblyEditor.PublicizeType(typeDef))
{
- publicizedAnyMemberInAssembly = true;
- publicizedTypesCount++;
logger.Verbose($"Explicitly publicizing type: {typeName}");
}
continue;
@@ -474,20 +459,16 @@ private static bool PublicizeAssembly(ModuleDef module, PublicizerAssemblyContex
continue;
}
- if (AssemblyEditor.PublicizeType(typeDef))
- {
- publicizedAnyMemberInAssembly = true;
- publicizedTypesCount++;
- }
+ assemblyEditor.PublicizeType(typeDef);
}
}
- logger.Info("Publicized types: " + publicizedTypesCount);
- logger.Info("Publicized properties: " + publicizedPropertiesCount);
- logger.Info("Publicized methods: " + publicizedMethodsCount);
- logger.Info("Publicized fields: " + publicizedFieldsCount);
+ logger.Info("Publicized types: " + assemblyEditor.publicizedTypesCount);
+ logger.Info("Publicized properties: " + assemblyEditor.publicizedPropertiesCount);
+ logger.Info("Publicized methods: " + assemblyEditor.publicizedMethodsCount);
+ logger.Info("Publicized fields: " + assemblyEditor.publicizedFieldsCount);
- return publicizedAnyMemberInAssembly;
+ return assemblyEditor.publicizedAnyMemberInAssembly;
}
private static bool IsCompilerGenerated(IHasCustomAttribute memberDef)
diff --git a/src/Publicizer/PublicizerAssemblyContext.cs b/src/Publicizer/PublicizerAssemblyContext.cs
index 948ff4e..ae8c371 100644
--- a/src/Publicizer/PublicizerAssemblyContext.cs
+++ b/src/Publicizer/PublicizerAssemblyContext.cs
@@ -14,6 +14,7 @@ internal PublicizerAssemblyContext(string assemblyName)
internal bool ExplicitlyPublicizeAssembly { get; set; } = false;
internal bool IncludeCompilerGeneratedMembers { get; set; } = true;
internal bool IncludeVirtualMembers { get; set; } = true;
+ internal bool AddOriginalAccessModifierAttribute { get; set; } = true;
internal bool ExplicitlyDoNotPublicizeAssembly { get; set; } = false;
internal HashSet PublicizeMemberPatterns { get; } = new HashSet();
internal Regex? PublicizeMemberRegexPattern { get; set; }