harden(vtag): exclude backslash from passthrough capture + parity tests (A review)
This commit is contained in:
@@ -7,10 +7,17 @@ using System.Text.RegularExpressions;
|
|||||||
/// which can be evaluated by returning the dependency value directly — no Roslyn compilation.
|
/// which can be evaluated by returning the dependency value directly — no Roslyn compilation.
|
||||||
/// Narrow, exact pattern: any near-miss returns false and falls through to the Roslyn path.
|
/// Narrow, exact pattern: any near-miss returns false and falls through to the Roslyn path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Physically defined in the Core.Scripting.Abstractions assembly (Roslyn-free, so ControlPlane
|
||||||
|
/// can reference it); the namespace is Core.Scripting to keep consumer using-directives unchanged.
|
||||||
|
/// </remarks>
|
||||||
public static partial class PassthroughScript
|
public static partial class PassthroughScript
|
||||||
{
|
{
|
||||||
// ^ \s* return \s+ ctx . GetTag ( "X" ) . Value ; \s* $ (whitespace-tolerant around tokens)
|
// ^ \s* return \s+ ctx . GetTag ( "X" ) . Value ; \s* $ (whitespace-tolerant around tokens)
|
||||||
[GeneratedRegex(@"^\s*return\s+ctx\s*\.\s*GetTag\s*\(\s*""([^""]+)""\s*\)\s*\.\s*Value\s*;\s*$")]
|
// Tag-name class [^"\\] excludes both the closing quote and backslash: a literal containing a
|
||||||
|
// backslash escape (e.g. "a\\b" → runtime name a\b) won't match, so it correctly falls through
|
||||||
|
// to Roslyn, which interprets the escape and resolves the actual dependency key.
|
||||||
|
[GeneratedRegex(@"^\s*return\s+ctx\s*\.\s*GetTag\s*\(\s*""([^""\\]+)""\s*\)\s*\.\s*Value\s*;\s*$")]
|
||||||
private static partial Regex MirrorRegex();
|
private static partial Regex MirrorRegex();
|
||||||
|
|
||||||
/// <summary>True if <paramref name="source"/> is the mirror passthrough shape; outputs the referenced tag.</summary>
|
/// <summary>True if <paramref name="source"/> is the mirror passthrough shape; outputs the referenced tag.</summary>
|
||||||
|
|||||||
@@ -76,4 +76,26 @@ public sealed class PassthroughScriptTests
|
|||||||
PassthroughScript.TryMatch(source, out var tag).ShouldBeFalse();
|
PassthroughScript.TryMatch(source, out var tag).ShouldBeFalse();
|
||||||
tag.ShouldBe(string.Empty);
|
tag.ShouldBe(string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A tag literal containing a backslash escape (C# source <c>"a\\b"</c> → runtime name
|
||||||
|
/// <c>a\b</c>) does NOT match the passthrough pattern — it falls through to Roslyn, which
|
||||||
|
/// interprets the escape and resolves the correct dependency key. Capturing the raw source
|
||||||
|
/// text <c>a\\b</c> would produce a wrong-result silent miss against the key <c>a\b</c>.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void Rejects_tag_literal_containing_backslash_escape()
|
||||||
|
{
|
||||||
|
// C# literal "return ctx.GetTag(\"a\\\\b\").Value;" → script source contains: a\\b (two chars: backslash + b)
|
||||||
|
PassthroughScript.TryMatch("return ctx.GetTag(\"a\\\\b\").Value;", out var tag).ShouldBeFalse();
|
||||||
|
tag.ShouldBe(string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A plain dotted tag name (no backslash) still matches — the fix is additive only.</summary>
|
||||||
|
[Fact]
|
||||||
|
public void Matches_plain_dotted_tag_after_backslash_fix()
|
||||||
|
{
|
||||||
|
PassthroughScript.TryMatch("return ctx.GetTag(\"Site1.Area.Tag\").Value;", out var tag).ShouldBeTrue();
|
||||||
|
tag.ShouldBe("Site1.Area.Tag");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
@@ -200,6 +200,38 @@ public sealed class RoslynVirtualTagEvaluatorTests
|
|||||||
result.Reason.ShouldBeNull();
|
result.Reason.ShouldBeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Direct parity test: the EXACT mirror source <c>return ctx.GetTag("missing").Value;</c>
|
||||||
|
/// evaluated against an empty dependency dictionary takes the fast-path and yields
|
||||||
|
/// <c>Ok(null)</c>; a minimally-altered but semantically-identical non-passthrough variant
|
||||||
|
/// <c>return (object?)ctx.GetTag("missing").Value;</c> compiled by Roslyn against the same
|
||||||
|
/// empty deps yields the identical <c>Ok(null)</c> — proving the fast-path is byte-identical
|
||||||
|
/// to the Roslyn path for the missing-dependency case.</summary>
|
||||||
|
[Fact]
|
||||||
|
public void Passthrough_exact_mirror_missing_dep_matches_Roslyn_baseline()
|
||||||
|
{
|
||||||
|
using var sut = new RoslynVirtualTagEvaluator(NullLogger<RoslynVirtualTagEvaluator>.Instance);
|
||||||
|
|
||||||
|
// Fast-path: exact mirror shape → passthrough returns Ok(null) for missing dep.
|
||||||
|
var fastPath = sut.Evaluate(
|
||||||
|
"vt-parity-fast",
|
||||||
|
"return ctx.GetTag(\"missing\").Value;",
|
||||||
|
new Dictionary<string, object?>());
|
||||||
|
|
||||||
|
fastPath.Success.ShouldBeTrue(fastPath.Reason);
|
||||||
|
fastPath.Value.ShouldBeNull();
|
||||||
|
fastPath.Reason.ShouldBeNull();
|
||||||
|
|
||||||
|
// Roslyn path: `(object?)` cast forces compilation but reads the same missing tag.
|
||||||
|
var roslynPath = sut.Evaluate(
|
||||||
|
"vt-parity-roslyn",
|
||||||
|
"return (object?)ctx.GetTag(\"missing\").Value;",
|
||||||
|
new Dictionary<string, object?>());
|
||||||
|
|
||||||
|
roslynPath.Success.ShouldBeTrue(roslynPath.Reason);
|
||||||
|
roslynPath.Value.ShouldBeNull();
|
||||||
|
roslynPath.Reason.ShouldBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Decisive proof the fast-path skips Roslyn: the compiled-script cache (which every
|
/// <summary>Decisive proof the fast-path skips Roslyn: the compiled-script cache (which every
|
||||||
/// Roslyn evaluation populates via <c>GetOrAdd</c>) stays EMPTY after a mirror evaluation, then
|
/// Roslyn evaluation populates via <c>GetOrAdd</c>) stays EMPTY after a mirror evaluation, then
|
||||||
/// grows to one entry once a genuine (non-mirror) expression forces compilation.</summary>
|
/// grows to one entry once a genuine (non-mirror) expression forces compilation.</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user