public static class Consoler
{
// 隔離不同請求的輸出
public static ConcurrentDictionary Outputs { get; } = new();
public List Output { get; } = new List();
public void Write(object o) => Output.Add(o?.ToString() ?? "");
public void WriteLine(object o) => Output.Add((o?.ToString() ?? "") + "\n");
public static Consoler GetOrCreate(string sessionId) =>
Outputs.GetOrAdd(sessionId, _ => new Consoler());
public static List TakeOutput(string sessionId)
{
if (Outputs.TryRemove(sessionId, out var c))
return c.Output;
return new List();
}
}
public class CodeRunner
{
public List Run(string code, string sessionId, params string[] extraAssemblies)
{
var consoler = Consoler.GetOrCreate(sessionId);
// 1. 置換 Console 輸出
code = code.Replace("Console.WriteLine", $"Consoler.GetOrCreate(\"{sessionId}\").WriteLine");
code = code.Replace("Console.Write", $"Consoler.GetOrCreate(\"{sessionId}\").Write");
// 2. 編譯參數
var cp = new CompilerParameters
{
GenerateInMemory = true,
TreatWarningsAsErrors = false,
CompilerOptions = "/target:exe /optimize",
ReferencedAssemblies = { "mscorlib.dll", "System.dll", "System.Core.dll" }
};
foreach (var asm in extraAssemblies) cp.ReferencedAssemblies.Add(asm);
// 3. 編譯
var provider = new CSharpCodeProvider(new Dictionary { { "CompilerVersion", "v4.0" } });
var results = provider.CompileAssemblyFromSource(cp, code);
// 4. 處理編譯錯誤
if (results.Errors.HasErrors)
{
foreach (CompilerError err in results.Errors)
consoler.WriteLine($"錯誤 {err.Line}: {err.ErrorText}");
return Consoler.TakeOutput(sessionId);
}
// 5. 執行
try
{
var asm = results.CompiledAssembly;
var entry = asm.EntryPoint; // 尋找 Main
if (entry != null)
entry.Invoke(null, new object[] { Array.Empty() }); // 呼叫 Main()
}
catch (Exception ex)
{
consoler.WriteLine($"執行錯誤: {ex.Message}");
}
return Consoler.TakeOutput(sessionId);
}
}
// NuGet: Microsoft.CodeAnalysis.CSharp.Scripting
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
public class RoslynRunner
{
public async Task RunAsync(string code)
{
var options = ScriptOptions.Default
.WithReferences(typeof(object).Assembly, typeof(Console).Assembly, typeof(Enumerable).Assembly)
.WithImports("System", "System.Linq", "System.Collections.Generic", "System.Text");
// 建立自訂 Console 重導
var output = new StringBuilder();
var customConsole = new CustomConsole(output);
var globals = new { Console = customConsole };
var script = CSharpScript.Create(code, options, globalsType: typeof(Globals));
var result = await script.RunAsync(globals);
return output.ToString();
}
private class CustomConsole
{
private readonly StringBuilder _sb;
public CustomConsole(StringBuilder sb) => _sb = sb;
public void Write(object o) => _sb.Append(o);
public void WriteLine(object o) => _sb.AppendLine(o?.ToString());
}
private class Globals { public CustomConsole Console; }
}
Roslyn 優勢:**編譯快、支援 C# 新語法、Scripting API 直覺、可逐行互動 (REPL)**。
| 風險 | 緩解措施 |
|---|---|
| 惡意程式碼 (檔案/網路/進程) | 沙箱:AppDomain 隔離 (Legacy) / 容器化 / gVisor / WASM |
| 無限迴圈/記憶體耗盡 | 執行逾時 (`CancellationToken`)、記憶體限制 (Job Object / cgroups) |
| 敏感資訊洩露 | 移除敏感組件參考、過濾 `using`、審計日誌 |
| 編譯資源耗盡 | 限制並發編譯數、快取編譯結果 |