fix(scripting+alarms): close remaining re-review findings
Single commit covering the four small/medium fixes from the updated code review. Core.Scripting-014 (Medium, Concurrency): CompiledScriptCache.Clear() used the key-only TryRemove(key, out var lazy) overload — same race shape Core.Scripting-006 closed in GetOrCompile's catch block. A concurrent re-add between snapshot and TryRemove was evicted + disposed while the new caller still held it. Replaced with the value-scoped TryRemove(KeyValuePair<,>) overload. Regression test Clear_uses_value_scoped_TryRemove_so_a_race_inserted_entry_survives added. Core.Scripting-013 (Medium, Security): Hand-rolled BuildWrapperSource pastes user source between literal braces; brace-balanced source could inject sibling methods/classes alongside CompiledScript.Run. Analyzer still walked the injected members so it wasn't a direct escape, but it relaxed the documented 'method body' authoring contract. Added EnforceSingleRunMember: after ParseText, the compilation unit must hold exactly one type (CompiledScript) and that type must hold exactly one member (the Run method). Any deviation throws CompilationErrorException with LMX001/ LMX002 diagnostic IDs and a Core.Scripting-013 reference in the message. Two regression tests added covering the sibling-method and sibling-class injection vectors. Core.Scripting-015 (Low, Correctness, latent): ToCSharpTypeName's generic branch truncated at the first backtick via IndexOf, silently dropping closed args of nested-generic shapes (Outer<T>.Inner<U>). No production caller exercises this shape today (all TContext/TResult are top-level non-nested), so the bug was latent. Rewrote the generic branch to walk the FullName segment-by- segment, consuming generic args per segment so nested shapes emit valid C# (global::Ns.Outer<T>.Inner<U> rather than the broken Outer<T,U>). Core.ScriptedAlarms-013 (Low, Documentation): The internal test accessors TryGetScratchReadCacheForTest / TryGetScratchContextForTest return live mutable scratch refilled in place under _evalGate. XML docs didn't warn future test authors about the synchronization contract. Added a <remarks> block to each documenting the only-safe-on-quiesced-engine + identity-or-single-key contract. Verification (suites green): Core.Scripting.Tests: 110/110 (was 107 — +3 new rejection/race tests) Core.ScriptedAlarms.Tests: 67/67 (unchanged — doc-only fix) Core.VirtualTags.Tests: 57/57 (unchanged) After this commit, all 12 findings from the updated re-review are closed (10 Resolved, 1 Won't Fix none, 1 Deferred — Driver.Galaxy-017). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -494,4 +494,40 @@ public sealed class ScriptSandboxTests
|
||||
return 0;
|
||||
"""));
|
||||
}
|
||||
|
||||
// --- Core.Scripting-013: wrapper-source injection ---
|
||||
|
||||
[Fact]
|
||||
public void Rejects_sibling_method_injection_via_balanced_braces()
|
||||
{
|
||||
// Core.Scripting-013 — a brace-balanced source that closes Run early and
|
||||
// declares a sibling method inside CompiledScript. The analyzer would still
|
||||
// walk the injected member, but the documented "method body" contract
|
||||
// forbids the shape outright now.
|
||||
var ex = Should.Throw<CompilationErrorException>(() =>
|
||||
ScriptEvaluator<FakeScriptContext, int>.Compile(
|
||||
"""
|
||||
return 0;
|
||||
} public static int Evil() { return 1;
|
||||
"""));
|
||||
ex.Message.ShouldContain("Core.Scripting-013");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rejects_sibling_class_injection_via_balanced_braces()
|
||||
{
|
||||
// Same shape, but injecting an entire sibling class inside the synthesized
|
||||
// namespace. Reject at the type-count check.
|
||||
var ex = Should.Throw<CompilationErrorException>(() =>
|
||||
ScriptEvaluator<FakeScriptContext, int>.Compile(
|
||||
"""
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public static class CompiledScript2 { public static int M() { return 0; } }
|
||||
namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Compiled { public static class CompiledScript3 { public static int M() {
|
||||
return 0;
|
||||
"""));
|
||||
ex.Message.ShouldContain("Core.Scripting-013");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user