using System;
using System.Collections.Generic;
using System.Linq;
using UnityPipeline.Microsoft.CodeAnalysis;
using UnityPipeline.Microsoft.CodeAnalysis.CSharp;
using UnityPipeline.Microsoft.CodeAnalysis.CSharp.Syntax;
using UnityEngine;
namespace Unity.Pipeline.CodeReload
{
///
/// Accessibility gate for [CodeReload] method bodies on the compiled (Assembly.Load) backend.
///
/// The compiled backend cannot use non-public members of the target type: even if the override
/// compiled with Roslyn's access checks relaxed, Unity's Mono enforces accessibility when
/// JIT-ing the loaded override IL and does not honor [assembly: IgnoresAccessChecksTo]
/// (verified on Mono 6.13) — the access would throw (e.g. FieldAccessException) on first
/// dispatch and silently fall back to the original body. Compiled-backend overrides therefore
/// compile with standard access checks (see
/// RoslynCompilationService.AllowNonPublicMemberAccess, interpreter-only), and this
/// validator runs up front — only when the compiled backend will execute the override — to turn
/// the eventual raw CS0122 into a clear reload error. The interpreter backend reaches
/// non-public members via reflection and must not be gated.
///
static class AccessibilityValidator
{
///
/// Check that every [CodeReload] method body only accesses public instance members of its
/// declaring type. Runs over an already-built semantic model + class declaration, so the
/// in-place reload path shares one compilation between validation and transformation
/// instead of building two.
///
/// The semantic model built over the source containing the declaring type.
/// The declaring type's class declaration within .
/// Map of method name to body source, for the methods being validated.
/// Name of the declaring type to validate against.
/// The validation result, including any violations found.
public static AccessibilityValidationResult ValidatePublicAccess(
SemanticModel model,
ClassDeclarationSyntax classDecl,
Dictionary methodBodies,
string originalTypeName)
{
var result = new AccessibilityValidationResult
{
IsValid = true,
Violations = new List()
};
try
{
var classSymbol = model.GetDeclaredSymbol(classDecl);
foreach (var method in classDecl.Members.OfType())
{
var methodName = method.Identifier.ValueText;
if (!methodBodies.ContainsKey(methodName) || method.Body == null)
continue;
var seen = new HashSet();
foreach (var name in method.Body.DescendantNodes().OfType())
{
if (IsMemberName(name))
continue;
var symbol = model.GetSymbolInfo(name).Symbol;
if (!IsInstanceMemberOf(symbol, classSymbol))
continue;
if (symbol.DeclaredAccessibility != Accessibility.Public && seen.Add(symbol.Name))
{
result.Violations.Add(new AccessibilityViolation
{
MemberName = symbol.Name,
MethodName = methodName,
AccessLevel = symbol.DeclaredAccessibility,
ViolationType = AccessibilityViolationType.PrivateAccess,
ErrorMessage = $"Cannot access non-public member '{symbol.Name}' " +
$"({symbol.DeclaredAccessibility}) in [CodeReload] method '{methodName}'",
Suggestion = $"Make '{symbol.Name}' public in {originalTypeName}, or reload " +
"through the interpreter backend (reload_file_editor_interpreter), which " +
"reaches non-public members. The compiled backend loads the override " +
"as a separate assembly and Mono enforces accessibility at dispatch."
});
}
}
}
result.IsValid = result.Violations.Count == 0;
return result;
}
catch (Exception ex)
{
Debug.LogError($"CodeReload: Accessibility validation error: {ex.Message}");
return new AccessibilityValidationResult
{
IsValid = false,
ValidationError = ex.Message,
Violations = new List()
};
}
}
/// True if the name is the right-hand member name of an access (foo.Bar -> Bar).
private static bool IsMemberName(SimpleNameSyntax node)
{
if (node.Parent is MemberAccessExpressionSyntax ma && ma.Name == node)
return true;
if (node.Parent is QualifiedNameSyntax)
return true;
if (node.Parent is MemberBindingExpressionSyntax)
return true;
return false;
}
/// True if the symbol is an instance field/property/method/event of the type or a base.
private static bool IsInstanceMemberOf(ISymbol symbol, INamedTypeSymbol type)
{
if (symbol == null || symbol.IsStatic)
return false;
switch (symbol.Kind)
{
case SymbolKind.Field:
case SymbolKind.Property:
case SymbolKind.Method:
case SymbolKind.Event:
break;
default:
return false;
}
for (var t = type; t != null; t = t.BaseType)
{
if (SymbolEqualityComparer.Default.Equals(t, symbol.ContainingType))
return true;
}
return false;
}
}
///
/// Result of accessibility validation for code reload methods.
///
class AccessibilityValidationResult
{
/// True when no accessibility violations were found.
public bool IsValid { get; set; }
/// Violations found, if any.
public List Violations { get; set; } = new List();
/// Set when validation itself could not run (e.g. a parse failure), rather than a violation being found.
public string ValidationError { get; set; }
/// Human-readable summary of or all .
public string GetFormattedErrorMessage()
{
if (!string.IsNullOrEmpty(ValidationError))
return $"CodeReload Validation Error: {ValidationError}";
if (Violations.Count == 0)
return "All member access is valid for code reload.";
var errorMessage = $"CodeReload Validation Failed: {Violations.Count} accessibility violation(s) found\n\n";
for (int i = 0; i < Violations.Count; i++)
{
var violation = Violations[i];
errorMessage += $"{i + 1}. Method '{violation.MethodName}': {violation.ErrorMessage}\n";
errorMessage += $" → Suggestion: {violation.Suggestion}\n";
if (i < Violations.Count - 1)
errorMessage += "\n";
}
errorMessage += "\nFix these accessibility issues and run reload_file again.";
return errorMessage;
}
}
///
/// Information about a specific accessibility violation in code reload code.
///
class AccessibilityViolation
{
/// Name of the non-public member that was accessed.
public string MemberName { get; set; }
/// Name of the [CodeReload] method containing the access.
public string MethodName { get; set; }
/// The member's actual accessibility.
public Accessibility AccessLevel { get; set; }
/// Category of violation.
public AccessibilityViolationType ViolationType { get; set; }
/// Human-readable description of the violation.
public string ErrorMessage { get; set; }
/// Suggested fix.
public string Suggestion { get; set; }
}
///
/// Types of accessibility violations that can occur in code reload methods.
///
enum AccessibilityViolationType
{
/// A private member was accessed.
PrivateAccess,
/// An internal member was accessed.
InternalAccess,
/// A protected member was accessed.
ProtectedAccess,
/// The source could not be parsed to determine accessibility.
ParseError
}
}