Skip to content

Commit 46e98b3

Browse files
EmandMNoelStephensUnityjabbacakes
authored
fix: Private nested types in NetworkVariables in CoreCLR (#4239)
* fix: Private nested types in NetworkVariables in CoreCLR * Update CHANGELOG and upgrade-guide * Update com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md Co-authored-by: Amy Reeve <amy.reeve@unity3d.com> * Fix U-PR comments --------- Co-authored-by: Noel Stephens <noel.stephens@unity3d.com> Co-authored-by: Amy Reeve <amy.reeve@unity3d.com>
1 parent 2676f2b commit 46e98b3

4 files changed

Lines changed: 86 additions & 0 deletions

File tree

‎com.unity.netcode.gameobjects/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1818

1919
### Fixed
2020

21+
- Ensured a useful and human readable error is logged when a NetworkVariable is trying to serialize a private or protected type. (#4239)
2122
- Issue where `NetworkAnimator` logged an error for a trigger-conditioned transition into an Exit node, and a client that joined during that transition, or during one into a sub-state machine, did not synchronize with it. Transitions that pass through Exit, Entry or sub-state machine nodes now resolve to the state they reach, and a late-joining client cross-fades from the matching originating state when several states transition into the same destination. (#4238)
2223
- Ensured all callbacks are wrapped with exception handling to avoid silent errors. (#4161)
2324

‎com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ In version 2.x, the `Lerp` and `SmoothDampening` interpolation types smooth by d
123123
124124
Version 3.x introduces the foundations of the unified GameObject layer for Netcode for Entities, known as the hybrid prefab concept. Version 3.x doesn't enable this integration by default for standard GameObject workflows. Standard `NetworkBehaviour`, `NetworkVariable`, and RPC workflows work the same as in version 2.x.
125125

126+
## CoreCLR compilation changes
127+
128+
CoreCLR introduces a new compilation pipeline that changes some custom serialization generation.
129+
130+
### NetworkVariable serialization
131+
132+
The Mono compilation pipeline was capable of generating serialization for private and protected types. With CoreCLR, this is no longer possible and will fail the compilation. Ensure any types used inside NetworkVariables in your project are declared as `public` or `internal`.
133+
126134
## Additional resources
127135

128136
- [Install Netcode for GameObjects](install.md)

‎com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,61 @@ public static bool HasInterface(this TypeReference typeReference, string interfa
208208
}
209209
}
210210

211+
/// <summary>
212+
/// Collects every type that assembly scoped code cannot access when it uses <paramref name="type"/>.
213+
/// This is each private or protected nested type in its nesting chain, or in the chain of its generic arguments.
214+
/// A protected internal type is only hidden when it is declared in an assembly whose internals <paramref name="consumer"/> cannot see.
215+
/// </summary>
216+
/// <param name="type">The type the generated code uses.</param>
217+
/// <param name="consumer">The assembly the generated code is emitted into.</param>
218+
/// <param name="hidden">Receives each inaccessible type, without duplicates.</param>
219+
public static void CollectTypesHiddenFromAssembly(this TypeReference type, AssemblyDefinition consumer, List<TypeDefinition> hidden)
220+
{
221+
if (type is GenericInstanceType genericInstance)
222+
{
223+
foreach (var argument in genericInstance.GenericArguments)
224+
{
225+
argument.CollectTypesHiddenFromAssembly(consumer, hidden);
226+
}
227+
}
228+
else if (type is TypeSpecification specification)
229+
{
230+
specification.ElementType.CollectTypesHiddenFromAssembly(consumer, hidden);
231+
return;
232+
}
233+
234+
for (var definition = type.Resolve(); definition != null && definition.IsNested; definition = definition.DeclaringType)
235+
{
236+
var isHidden = definition.IsNestedPrivate || definition.IsNestedFamily || definition.IsNestedFamilyAndAssembly ||
237+
(definition.IsNestedFamilyOrAssembly && !consumer.CanSeeInternalsOf(definition.Module.Assembly));
238+
if (isHidden && !hidden.Contains(definition))
239+
{
240+
hidden.Add(definition);
241+
}
242+
}
243+
}
244+
245+
/// <summary>
246+
/// Checks whether code in <paramref name="consumer"/> can access the internal members of <paramref name="owner"/>.
247+
/// </summary>
248+
/// <param name="consumer">The assembly that accesses the members.</param>
249+
/// <param name="owner">The assembly that declares the members.</param>
250+
/// <returns>True if both are the same assembly, or <paramref name="owner"/> grants <paramref name="consumer"/> InternalsVisibleTo.</returns>
251+
private static bool CanSeeInternalsOf(this AssemblyDefinition consumer, AssemblyDefinition owner)
252+
{
253+
if (owner.Name.Name == consumer.Name.Name)
254+
{
255+
return true;
256+
}
257+
258+
// InternalsVisibleTo takes "Name" or "Name, PublicKey=..."; only the simple name matters here.
259+
return owner.CustomAttributes.Any(attribute =>
260+
attribute.AttributeType.FullName == typeof(System.Runtime.CompilerServices.InternalsVisibleToAttribute).FullName &&
261+
attribute.ConstructorArguments.Count == 1 &&
262+
attribute.ConstructorArguments[0].Value is string friend &&
263+
friend.Split(',')[0].Trim() == consumer.Name.Name);
264+
}
265+
211266
/// <summary>
212267
/// Checks a generic method instantiation against the generic constraints of the method it instantiates.
213268
/// Mono does not enforce these on the calls codegen emits, but CoreCLR does, and it rejects the entire

‎com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,28 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly,
437437
continue;
438438
}
439439

440+
// CoreCLR enforces member access on the generic calls emitted below; Mono does not.
441+
var hiddenTypes = new List<TypeDefinition>();
442+
type.CollectTypesHiddenFromAssembly(assembly, hiddenTypes);
443+
if (hiddenTypes.Count > 0)
444+
{
445+
// Use the target assembly's defines: a Mono editor can build a CoreCLR player.
446+
if (assemblyDefines.Contains("ENABLE_CORECLR"))
447+
{
448+
foreach (var hiddenType in hiddenTypes)
449+
{
450+
m_Diagnostics.AddError($"{type} cannot be used in a network variable - Cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public.");
451+
}
452+
continue;
453+
}
454+
455+
// Log a warning in Mono so we can warn users who haven't yet switched their projects over.
456+
foreach (var hiddenType in hiddenTypes)
457+
{
458+
m_Diagnostics.AddWarning($"NetworkVariable serialization for {type.Name} will fail in CoreCLR! With CoreCLR generated code cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public.");
459+
}
460+
}
461+
440462
if (serializeMethod != null)
441463
{
442464
instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(serializeMethod)));

0 commit comments

Comments
 (0)