Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.vs
bin/
obj/
*.csproj.user
*.user
.idea/
183 changes: 175 additions & 8 deletions src/Publicizer/AssemblyEditor.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Class for making edits to assemblies and related types.
/// </summary>
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;
Expand All @@ -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;

Expand All @@ -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)
};
}
}
65 changes: 23 additions & 42 deletions src/Publicizer/PublicizeAssemblies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,17 +221,20 @@ private static Dictionary<string, PublicizerAssemblyContext> GetPublicizerAssemb

private static bool PublicizeAssembly(ModuleDef module, PublicizerAssemblyContext assemblyContext, ITaskLogger logger)
{
bool publicizedAnyMemberInAssembly = false;
var doNotPublicizePropertyMethods = new HashSet<MethodDef>();

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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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++;
}
}
}
Expand All @@ -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;
Expand Down Expand Up @@ -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++;
}
}
}
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/Publicizer/PublicizerAssemblyContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> PublicizeMemberPatterns { get; } = new HashSet<string>();
internal Regex? PublicizeMemberRegexPattern { get; set; }
Expand Down