Skip to content

Commit 4e5b1bf

Browse files
sunnamed434claude
andcommitted
Don't rename/strip framework-reserved "magic" types (#97)
Compiler/runtime-recognised types (PolySharp-style polyfills such as System.Runtime.CompilerServices.IsExternalInit / RequiredMemberAttribute, System.Diagnostics.CodeAnalysis nullable attributes, System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute, System.Diagnostics.StackTraceHiddenAttribute, ...) are matched by their exact full name. Renaming them - or stripping their namespace - silently breaks the feature they back. Add TypeDefinition.IsInReservedNamespace() listing the framework-owned namespaces that only ever contain such shims in a user assembly, and skip them in both FullRenamer (Renamer.ShouldRenameType / RemoveNamespace) and NoNamespaces. Verified: polyfill types keep name+namespace while app types are still renamed and stripped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 36fff36 commit 4e5b1bf

3 files changed

Lines changed: 54 additions & 4 deletions

File tree

src/BitMono.Core/Renaming/Renamer.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ public string RenameUnsafely()
3131

3232
private bool ShouldRenameType(TypeDefinition? typeDefinition)
3333
{
34-
return typeDefinition == null || (_nameCriticalAnalyzer.NotCriticalToMakeChanges(typeDefinition) && !typeDefinition.IsExplicitLayout);
34+
// Types in framework-reserved namespaces (e.g. PolySharp polyfills) are matched by full
35+
// name by the compiler/runtime and must never be renamed. See IsInReservedNamespace.
36+
return typeDefinition == null || (_nameCriticalAnalyzer.NotCriticalToMakeChanges(typeDefinition) && !typeDefinition.IsExplicitLayout && !typeDefinition.IsInReservedNamespace());
3537
}
3638

3739
/// <summary>
@@ -89,7 +91,7 @@ public void RemoveNamespace(IMetadataMember member)
8991
{
9092
if (member is TypeDefinition type)
9193
{
92-
if (_specificNamespaceCriticalAnalyzer.NotCriticalToMakeChanges(type))
94+
if (_specificNamespaceCriticalAnalyzer.NotCriticalToMakeChanges(type) && !type.IsInReservedNamespace())
9395
{
9496
type.Namespace = string.Empty;
9597
}
@@ -98,15 +100,15 @@ public void RemoveNamespace(IMetadataMember member)
98100
{
99101
if (_nameCriticalAnalyzer.NotCriticalToMakeChanges(method))
100102
{
101-
if (method.DeclaringType != null)
103+
if (method.DeclaringType != null && !method.DeclaringType.IsInReservedNamespace())
102104
{
103105
method.DeclaringType.Namespace = string.Empty;
104106
}
105107
}
106108
}
107109
if (member is FieldDefinition field)
108110
{
109-
if (field.DeclaringType != null)
111+
if (field.DeclaringType != null && !field.DeclaringType.IsInReservedNamespace())
110112
{
111113
field.DeclaringType.Namespace = string.Empty;
112114
}

src/BitMono.Protections/NoNamespaces.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ public override Task ExecuteAsync()
1515
{
1616
continue;
1717
}
18+
// Keep framework-reserved namespaces (e.g. PolySharp polyfills) intact - the
19+
// compiler/runtime match those types by full name. See IsInReservedNamespace.
20+
if (type.IsInReservedNamespace())
21+
{
22+
continue;
23+
}
1824

1925
type.Namespace = string.Empty;
2026
}

src/BitMono.Utilities/AsmResolver/TypeDefinitionExtensions.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,48 @@ public static bool HasNamespace(this TypeDefinition source)
1111
return Utf8String.IsNullOrEmpty(source.Namespace) == false;
1212
}
1313

14+
// Compiler/runtime-recognised "magic" types are matched by their exact full name, so when a
15+
// project ships them as in-assembly polyfills/shims (e.g. PolySharp) they must keep their
16+
// original name AND namespace - renaming or namespace-stripping silently breaks the feature
17+
// they back. Real application code never lives in these framework-owned namespaces, so any
18+
// type defined under them is left untouched. See
19+
// https://github.com/sunnamed434/BitMono/issues/97.
20+
//
21+
// System.Runtime.CompilerServices - IsExternalInit, RequiredMemberAttribute,
22+
// CompilerFeatureRequiredAttribute, ModuleInitializerAttribute, SkipLocalsInitAttribute,
23+
// CallerArgumentExpressionAttribute, InterpolatedStringHandler*, CallConv* markers, ...
24+
// System.Diagnostics.CodeAnalysis - nullable analysis attributes (AllowNull, NotNull,
25+
// MemberNotNull, ...), DynamicallyAccessedMembers, RequiresUnreferencedCode, ...
26+
// System.Runtime.Versioning - SupportedOSPlatform/UnsupportedOSPlatform,
27+
// RequiresPreviewFeaturesAttribute, ...
28+
// System.Runtime.InteropServices - UnmanagedCallersOnlyAttribute is resolved by the runtime
29+
// by name to set up reverse P/Invoke; renaming it breaks the call.
30+
// System.Diagnostics - StackTraceHiddenAttribute is read by the runtime by name
31+
// when formatting stack traces.
32+
private static readonly string[] ReservedNamespaces =
33+
{
34+
"System.Runtime.CompilerServices",
35+
"System.Diagnostics.CodeAnalysis",
36+
"System.Runtime.Versioning",
37+
"System.Runtime.InteropServices",
38+
"System.Diagnostics",
39+
};
40+
41+
/// <summary>
42+
/// Whether the type lives in a framework-owned namespace that holds compiler/runtime
43+
/// "magic" types matched by full name (e.g. PolySharp polyfills). Such types must never be
44+
/// renamed nor have their namespace stripped.
45+
/// </summary>
46+
public static bool IsInReservedNamespace(this TypeDefinition? source)
47+
{
48+
var @namespace = source?.Namespace?.Value;
49+
if (string.IsNullOrEmpty(@namespace))
50+
{
51+
return false;
52+
}
53+
return Array.IndexOf(ReservedNamespaces, @namespace) != -1;
54+
}
55+
1456
/// <summary>
1557
/// Gets the type and all its base types in the inheritance hierarchy.
1658
/// </summary>

0 commit comments

Comments
 (0)