using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; namespace io.github.hatayama.uLoopMCP { /// /// Manages the caching of compilation results. /// Avoids recompiling the same code by hashing requests with SHA256. /// public class CompilationCacheManager { private const int MaxCacheEntries = 32; private readonly Dictionary _compilationCache = new(); private readonly Queue _cacheOrder = new(); public CompilationResult CheckCache(CompilationRequest request) { string cacheKey = GenerateCacheKey(request); if (_compilationCache.TryGetValue(cacheKey, out CachedCompilationResult cachedResult)) { return CloneCompilationResult(cachedResult); } return null; } public void CacheResultIfSuccessful(CompilationResult result, CompilationRequest request) { if (result.Success && result.CompiledAssembly != null) { string cacheKey = GenerateCacheKey(request); CachedCompilationResult cachedResult = CreateCachedCompilationResult(result); if (_compilationCache.ContainsKey(cacheKey)) { _compilationCache[cacheKey] = cachedResult; return; } // Dynamic assemblies cannot be unloaded from the default AppDomain, // so the cache keeps only a small hot set instead of retaining every snippet forever. if (_compilationCache.Count >= MaxCacheEntries) { string oldestKey = _cacheOrder.Dequeue(); _compilationCache.Remove(oldestKey); } _compilationCache[cacheKey] = cachedResult; _cacheOrder.Enqueue(cacheKey); } } public void ClearCache() { _compilationCache.Clear(); _cacheOrder.Clear(); } public string GenerateCacheKey(CompilationRequest request) { StringBuilder keyBuilder = new StringBuilder(); keyBuilder.Append(request.Code); keyBuilder.Append("|"); keyBuilder.Append(request.ClassName ?? ""); keyBuilder.Append("|"); keyBuilder.Append(request.Namespace ?? ""); if (request.AdditionalReferences != null && request.AdditionalReferences.Any()) { keyBuilder.Append("|"); keyBuilder.Append(string.Join(",", request.AdditionalReferences.OrderBy(r => r))); } using (SHA256 sha256 = SHA256.Create()) { byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(keyBuilder.ToString())); return Convert.ToBase64String(hashBytes); } } private static CachedCompilationResult CreateCachedCompilationResult(CompilationResult result) { return new CachedCompilationResult( result.CompiledAssembly, CloneCompilationErrors(result.Errors), CloneStrings(result.Warnings), result.UpdatedCode, result.HasSecurityViolations, CloneSecurityViolations(result.SecurityViolations), result.FailureReason, CloneAmbiguousTypeCandidates(result.AmbiguousTypeCandidates), CloneStrings(result.AutoInjectedNamespaces), CloneStrings(result.Timings), CloneStrings(result.AdvisoryLogs), result.CompilationBackendKind); } private static CompilationResult CloneCompilationResult(CachedCompilationResult cachedResult) { return new CompilationResult { Success = true, CompiledAssembly = cachedResult.CompiledAssembly, Errors = CloneCompilationErrors(cachedResult.Errors), Warnings = CloneStrings(cachedResult.Warnings), UpdatedCode = cachedResult.UpdatedCode, HasSecurityViolations = cachedResult.HasSecurityViolations, SecurityViolations = CloneSecurityViolations(cachedResult.SecurityViolations), FailureReason = cachedResult.FailureReason, AmbiguousTypeCandidates = CloneAmbiguousTypeCandidates(cachedResult.AmbiguousTypeCandidates), AutoInjectedNamespaces = CloneStrings(cachedResult.AutoInjectedNamespaces), Timings = BuildCachedCompilationTimings(cachedResult.CompilationBackendKind), AdvisoryLogs = CloneStrings(cachedResult.AdvisoryLogs), CompilationBackendKind = cachedResult.CompilationBackendKind }; } private static List BuildCachedCompilationTimings( DynamicCompilationBackendKind compilationBackendKind) { List timings = new List { "[Perf] ReferenceResolution: 0.0ms", "[Perf] Build: 0.0ms", "[Perf] AssemblyLoad: 0.0ms" }; switch (compilationBackendKind) { case DynamicCompilationBackendKind.SharedRoslynWorker: timings.Add("[Perf] Backend: SharedRoslynWorker"); break; case DynamicCompilationBackendKind.OneShotRoslyn: timings.Add("[Perf] Backend: OneShotRoslyn"); break; case DynamicCompilationBackendKind.AssemblyBuilderFallback: timings.Add("[Perf] Backend: AssemblyBuilderFallback"); break; } timings.Add("[Perf] CacheHit: true"); return timings; } private static List CloneCompilationErrors(List errors) { List clonedErrors = new List(); if (errors == null) { return clonedErrors; } foreach (CompilationError error in errors) { clonedErrors.Add(new CompilationError { Message = error.Message, Line = error.Line, Column = error.Column, ErrorCode = error.ErrorCode }); } return clonedErrors; } private static List CloneSecurityViolations(List securityViolations) { List clonedViolations = new List(); if (securityViolations == null) { return clonedViolations; } foreach (SecurityViolation violation in securityViolations) { clonedViolations.Add(new SecurityViolation { Type = violation.Type, Description = violation.Description, LineNumber = violation.LineNumber, CodeSnippet = violation.CodeSnippet, Message = violation.Message, ApiName = violation.ApiName, Location = violation.Location }); } return clonedViolations; } private static Dictionary> CloneAmbiguousTypeCandidates( Dictionary> ambiguousTypeCandidates) { Dictionary> clonedCandidates = new Dictionary>(); if (ambiguousTypeCandidates == null) { return clonedCandidates; } foreach (KeyValuePair> entry in ambiguousTypeCandidates) { clonedCandidates[entry.Key] = CloneStrings(entry.Value); } return clonedCandidates; } private static List CloneStrings(List values) { if (values == null) { return new List(); } return new List(values); } private sealed class CachedCompilationResult { public Assembly CompiledAssembly { get; } public List Errors { get; } public List Warnings { get; } public string UpdatedCode { get; } public bool HasSecurityViolations { get; } public List SecurityViolations { get; } public CompilationFailureReason FailureReason { get; } public Dictionary> AmbiguousTypeCandidates { get; } public List AutoInjectedNamespaces { get; } public List Timings { get; } public List AdvisoryLogs { get; } public DynamicCompilationBackendKind CompilationBackendKind { get; } public CachedCompilationResult( Assembly compiledAssembly, List errors, List warnings, string updatedCode, bool hasSecurityViolations, List securityViolations, CompilationFailureReason failureReason, Dictionary> ambiguousTypeCandidates, List autoInjectedNamespaces, List timings, List advisoryLogs, DynamicCompilationBackendKind compilationBackendKind) { CompiledAssembly = compiledAssembly; Errors = errors; Warnings = warnings; UpdatedCode = updatedCode; HasSecurityViolations = hasSecurityViolations; SecurityViolations = securityViolations; FailureReason = failureReason; AmbiguousTypeCandidates = ambiguousTypeCandidates; AutoInjectedNamespaces = autoInjectedNamespaces; Timings = timings; AdvisoryLogs = advisoryLogs; CompilationBackendKind = compilationBackendKind; } } } }