using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.ScriptAnalysis;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
///
/// WP-19: Script Trust Model — compiles C# scripts using Roslyn with restricted API access.
/// The forbidden-API verdict is delegated to the shared authoritative
/// (M3.1 consolidation); this service keeps the real
/// execution-path compile of the script against /
/// .
///
public class ScriptCompilationService
{
private readonly ILogger _logger;
/// Initializes a new instance of the ScriptCompilationService class.
/// Logger instance.
public ScriptCompilationService(ILogger logger)
{
_logger = logger;
}
///
/// SiteRuntime-011: validates that the script does not reference forbidden APIs.
///
/// As of the M3.1 script-analysis consolidation this delegates to the shared
/// authoritative ,
/// which is the same Roslyn semantic-symbol analysis this service previously hosted
/// plus reflection-gateway / dynamic / Activator hardening ported from
/// the InboundAPI checker. The shared validator is the single source of truth for the
/// forbidden-API deny-list; SiteRuntime retains only the real execution-path compile
/// in .
///
/// Returns a list of violation messages, empty if clean.
///
/// The script code to validate.
/// A list of trust-model violation messages; empty if the script is clean.
public IReadOnlyList ValidateTrustModel(string code)
=> ScriptTrustValidator.FindViolations(code);
///
/// Assemblies referenced by compiled scripts, used to build the Roslyn scripting
/// options for the real execution-path compile.
///
private static readonly System.Reflection.Assembly[] ScriptAssemblies =
[
typeof(object).Assembly,
typeof(Enumerable).Assembly,
typeof(Math).Assembly,
typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly,
typeof(Commons.Types.DynamicJsonElement).Assembly
];
///
/// Shared Roslyn scripting options (references + imports) used by both full
/// script compilation and trigger-expression compilation.
///
private static ScriptOptions BuildScriptOptions() => ScriptOptions.Default
.WithReferences(ScriptAssemblies)
.WithImports(
"System",
"System.Collections.Generic",
"System.Linq",
"System.Threading.Tasks");
///
/// Compiles a script into a reusable delegate that takes a ScriptRuntimeContext
/// and parameters dictionary, and returns an object? result.
///
/// The name of the script.
/// The script code to compile.
/// A containing the compiled script on success, or error messages on failure.
public ScriptCompilationResult Compile(string scriptName, string code)
=> CompileCore(scriptName, code, typeof(ScriptGlobals));
///
/// Compiles a bare C# boolean trigger expression against the restricted
/// read-only . The expression is a
/// trailing expression (no return); Roslyn scripting yields its
/// value, which the caller coerces to bool. Reuses the same script
/// options and forbidden-API trust validation as .
///
/// The name of the trigger expression.
/// The trigger expression to compile.
/// A containing the compiled expression on success, or error messages on failure.
public ScriptCompilationResult CompileTriggerExpression(string name, string expression)
=> CompileCore(name, expression, typeof(TriggerExpressionGlobals));
///
/// Shared compilation path: validates the trust model, builds the script
/// against the given globals type, and returns the compiled result.
///
private ScriptCompilationResult CompileCore(string name, string code, Type globalsType)
{
// Validate trust model
var violations = ValidateTrustModel(code);
if (violations.Count > 0)
{
_logger.LogWarning(
"Script {Script} failed trust validation: {Violations}",
name, string.Join("; ", violations));
return ScriptCompilationResult.Failed(violations);
}
try
{
var script = CSharpScript.Create