Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion OAT.Scripting/ScriptOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;

Expand All @@ -30,6 +31,12 @@ public ScriptOperation(Analyzer analyzer) : base(Operation.Script, analyzer)

internal IEnumerable<Violation> ScriptOperationValidationDelegate(Rule rule, Clause clause)
{
if (Analyzer?.Options.RunScripts != true)
{
yield return new Violation(string.Format(Strings.Get("Err_ScriptingDisabled_{0}{1}"), rule.Name, clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture)), rule, clause);
yield break;
}

if (clause.Script is ScriptData clauseScript)
{
var issues = new List<Violation>();
Expand All @@ -40,7 +47,21 @@ internal IEnumerable<Violation> ScriptOperationValidationDelegate(Rule rule, Cla
options = options.AddImports(clauseScript.Imports);
options = options.AddReferences(typeof(Analyzer).Assembly);

options = options.AddReferences(clauseScript.References.Select(Assembly.Load));
// Resolve assembly references without Assembly.Load to avoid triggering
// module initializers during validation (which should be side-effect free).
foreach (var reference in clauseScript.References)
{
var resolvedPath = ResolveAssemblyPath(reference);
if (resolvedPath != null)
{
options = options.AddReferences(MetadataReference.CreateFromFile(resolvedPath));
}
else
{
issues.Add(new Violation(string.Format(Strings.Get("Err_ClauseInvalidLambda_{0}{1}{2}"), rule.Name, clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture), $"Could not resolve assembly reference '{reference}'"), rule, clause));
}
}

var script = CSharpScript.Create<OperationResult>(clauseScript.Code, globalsType: typeof(OperationArguments), options: options);

foreach (var issue in script.Compile())
Expand All @@ -67,10 +88,47 @@ internal IEnumerable<Violation> ScriptOperationValidationDelegate(Rule rule, Cla
}
}

/// <summary>
/// Resolves an assembly name to a file path without loading it into the runtime.
/// This avoids triggering module initializers that Assembly.Load would execute.
/// </summary>
private static string? ResolveAssemblyPath(string assemblyName)
{
// Check already-loaded assemblies (no new loading occurs)
var loaded = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => string.Equals(a.GetName().Name, assemblyName, StringComparison.OrdinalIgnoreCase));
if (loaded != null && !string.IsNullOrEmpty(loaded.Location))
{
return loaded.Location;
}

// Try the application base directory
var basePath = Path.Combine(AppContext.BaseDirectory, assemblyName + ".dll");
if (File.Exists(basePath))
{
return basePath;
}

// Try the runtime directory
var runtimeDir = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
var runtimePath = Path.Combine(runtimeDir, assemblyName + ".dll");
if (File.Exists(runtimePath))
{
return runtimePath;
}

return null;
}

private Dictionary<ScriptData, Script<OperationResult>?> lambdas { get; } = new Dictionary<ScriptData, Script<OperationResult>?>();

internal OperationResult ScriptOperationDelegate(Clause clause, object? state1, object? state2, IEnumerable<ClauseCapture>? captures)
{
if (Analyzer?.Options.RunScripts != true)
{
return new OperationResult(false, null);
}

if (clause.Script is ScriptData scriptData)
{
if (!lambdas.ContainsKey(clause.Script))
Expand Down
49 changes: 28 additions & 21 deletions OAT/Operations/RegexOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,40 +71,47 @@ internal OperationResult RegexOperationDelegate(Clause clause, object? state1, o

if (regex != null)
{
foreach (var state in stateOneList)
try
{
var matches = regex.Matches(state);

if (matches.Count > 0 || (matches.Count == 0 && clause.Invert))
foreach (var state in stateOneList)
{
var outmatches = new List<Match>();
foreach (var match in matches)
var matches = regex.Matches(state);

if (matches.Count > 0 || (matches.Count == 0 && clause.Invert))
{
if (match is Match m)
var outmatches = new List<Match>();
foreach (var match in matches)
{
outmatches.Add(m);
if (match is Match m)
{
outmatches.Add(m);
}
}
return new OperationResult(true, !clause.Capture ? null : new TypedClauseCapture<List<Match>>(clause, outmatches, state1));
}
return new OperationResult(true, !clause.Capture ? null : new TypedClauseCapture<List<Match>>(clause, outmatches, state1));
}
}
foreach (var state in stateTwoList)
{
var matches = regex.Matches(state);

if (matches.Count > 0 || (matches.Count == 0 && clause.Invert))
foreach (var state in stateTwoList)
{
var outmatches = new List<Match>();
foreach (var match in matches)
var matches = regex.Matches(state);

if (matches.Count > 0 || (matches.Count == 0 && clause.Invert))
{
if (match is Match m)
var outmatches = new List<Match>();
foreach (var match in matches)
{
outmatches.Add(m);
if (match is Match m)
{
outmatches.Add(m);
}
}
return new OperationResult(true, !clause.Capture ? null : new TypedClauseCapture<List<Match>>(clause, outmatches, state2: state2));
}
return new OperationResult(true, !clause.Capture ? null : new TypedClauseCapture<List<Match>>(clause, outmatches, state2: state2));
}
}
catch (RegexMatchTimeoutException)
{
Log.Warning("Regex match timed out for pattern {0}. Treating as non-match.", built);
}
}
}
return new OperationResult(false, null);
Expand All @@ -122,7 +129,7 @@ internal OperationResult RegexOperationDelegate(Clause clause, object? state1, o
{
try
{
RegexCache.TryAdd((built, regexOptions), new Regex(built, regexOptions));
RegexCache.TryAdd((built, regexOptions), new Regex(built, regexOptions, TimeSpan.FromSeconds(5)));
}
catch (ArgumentException)
{
Expand Down
Loading