Skip to content

Commit 079e7d2

Browse files
author
Rajesh Jinaga
committed
Make script argument immutable based on config
1 parent 51d05ba commit 079e7d2

14 files changed

+279
-28
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) navtech.io. All rights reserved.
2+
// See License in the project root for license information.
3+
4+
namespace Simpleflow.CodeGenerator
5+
{
6+
internal static class ArgumentImmutabilityCheck
7+
{
8+
public static bool CheckForSameReference(object scriptArgument, object variable)
9+
{
10+
if (scriptArgument == null || variable == null )
11+
{
12+
return false;
13+
}
14+
15+
if (object.ReferenceEquals(scriptArgument, variable))
16+
{
17+
return true;
18+
}
19+
20+
if (scriptArgument != null)
21+
{
22+
// Deep dive for checking reference
23+
foreach (var prop in scriptArgument.GetType().GetProperties())
24+
{
25+
if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string))
26+
{
27+
var value = prop.GetValue(scriptArgument);
28+
if (value != null)
29+
{
30+
return CheckForSameReference(prop.GetValue(scriptArgument), variable);
31+
}
32+
}
33+
}
34+
}
35+
36+
return false;
37+
}
38+
}
39+
}

src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VisitEmitters.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public override Expression VisitOutputStmt(SimpleflowParser.OutputStmtContext co
4343
var name = context.objectIdentifier().GetText();
4444

4545
Expression callExpr = Expression.Call(
46-
instance: Expression.Property(Output, nameof(FlowOutput.Output)),
46+
instance: Expression.Property(OutputParam, nameof(FlowOutput.Output)),
4747

4848
// ReSharper disable once AssignNullToNotNullAttribute
4949
method: typeof(Dictionary<string, object>).GetMethod(
@@ -70,7 +70,7 @@ private Expression CallListAddMethod(Expression message, string propertyName)
7070
{
7171

7272
Expression callExpr = Expression.Call(
73-
Expression.Property(Output, propertyName),
73+
Expression.Property(OutputParam, propertyName),
7474
// ReSharper disable once AssignNullToNotNullAttribute
7575
typeof(List<string>).GetMethod(nameof(List<string>.Add), new Type[] { typeof(string) }),
7676
message

src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.VistitLetSet.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,6 @@ private Expression GetLetVariableExpression(SimpleflowParser.ExpressionContext c
259259

260260
private Expression VisitPartialSet(SimpleflowParser.SetStmtContext context, Expression variable)
261261
{
262-
// variable.Left.Type
263262
var pairs = context.expression().jsonObj().pair();
264263
List<Expression> propExpressions = new List<Expression>();
265264

@@ -269,8 +268,24 @@ private Expression VisitPartialSet(SimpleflowParser.SetStmtContext context, Expr
269268
(propInfo, valueExp) =>
270269
propExpressions.Add(Expression.Assign(Expression.Property(variable, propInfo), valueExp)));
271270

272-
// context.expression
273-
return Expression.Block(propExpressions);
271+
/*
272+
* Check for script argument immutability, exception case: if a function change property of script arg,
273+
* simppleflow does not control.
274+
*/
275+
var referenceProperties = new List<Expression>();
276+
var checkForSameReference = Expression.Call(null,
277+
typeof(ArgumentImmutabilityCheck).GetMethod(nameof(ArgumentImmutabilityCheck.CheckForSameReference)),
278+
arg0: InputParam,
279+
arg1: variable);
280+
281+
return
282+
Expression.IfThenElse(
283+
Expression.Or(
284+
Expression.IsTrue(Expression.Property(ScriptHelperContextParam, nameof(ScriptHelperContext.IsArgumentMutable))),
285+
Expression.IsFalse(checkForSameReference)),
286+
Expression.Block(propExpressions),
287+
Expression.Throw(Expression.New(typeof(ArgumentImmutableExeception)))
288+
);
274289
}
275290
}
276291
}

src/Simpleflow/CodeGenerator/SimpleflowCodeVisitor.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ internal partial class SimpleflowCodeVisitor<TArg> : SimpleflowParserBaseVisitor
2323
protected readonly List<ParameterExpression> Variables; // program variables
2424
protected readonly List<SmartJsonObjectParameterExpression> SmartJsonVariables; // program variables
2525

26-
protected readonly ParameterExpression Input; // script main function parameter 1
27-
protected readonly ParameterExpression Output; // script main function parameter 2
28-
protected readonly ParameterExpression ScriptHelperContext; //script main function parameter 3
26+
protected readonly ParameterExpression InputParam; // script main function parameter 1
27+
protected readonly ParameterExpression OutputParam; // script main function parameter 2
28+
protected readonly ParameterExpression ScriptHelperContextParam; //script main function parameter 3
2929

3030
protected readonly ParserEventPublisher EventPublisher;
3131

@@ -39,10 +39,10 @@ public SimpleflowCodeVisitor(IFunctionRegister functionRegister, ParserEventPubl
3939
SmartJsonVariables = new List<SmartJsonObjectParameterExpression>();
4040

4141
/* Initialize Function parameters */
42-
Input = Expression.Parameter(typeof(TArg));
43-
Output = Expression.Parameter(typeof(FlowOutput));
42+
InputParam = Expression.Parameter(typeof(TArg));
43+
OutputParam = Expression.Parameter(typeof(FlowOutput));
4444
// use context parameter name in order to access in script
45-
ScriptHelperContext = Expression.Parameter(typeof(ScriptHelperContext), "context");
45+
ScriptHelperContextParam = Expression.Parameter(typeof(ScriptHelperContext), "context");
4646

4747

4848
/* A label expression of the void type that is the target for Expression.Return(). */
@@ -72,7 +72,7 @@ public override Expression VisitProgram(SimpleflowParser.ProgramContext context)
7272
Expression<Action<TArg, FlowOutput, ScriptHelperContext>> program =
7373
Expression.Lambda<Action<TArg /*input-context*/, FlowOutput, ScriptHelperContext>>(
7474
body,
75-
new ParameterExpression[] { Input, Output, ScriptHelperContext }
75+
new ParameterExpression[] { InputParam, OutputParam, ScriptHelperContextParam }
7676
);
7777

7878
return program;
@@ -238,11 +238,11 @@ private List<Expression> CreateDefaultVariablesAndAssign()
238238
var argVar = Expression.Variable(typeof(TArg), "arg");
239239

240240
Variables.Add(argVar); // arg
241-
Variables.Add(ScriptHelperContext); // script
241+
Variables.Add(ScriptHelperContextParam); // script
242242

243243
return new List<Expression>()
244244
{
245-
Expression.Assign(argVar, Input)
245+
Expression.Assign(argVar, InputParam)
246246
};
247247
}
248248

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) navtech.io. All rights reserved.
2+
// See License in the project root for license information.
3+
4+
using System;
5+
6+
namespace Simpleflow.Exceptions
7+
{
8+
/// <summary>
9+
/// Represents base exception for all Simpleflow exceptions
10+
/// </summary>
11+
public class ArgumentImmutableExeception : Exception
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="SimpleflowException"/> class with
15+
/// a specified variable name.
16+
/// </summary>
17+
/// <param name="message">The message that describes the error.</param>
18+
public ArgumentImmutableExeception(): base($"Script argument cannot be mutable in this context. Set {nameof(FlowOptions.AllowArgumentToMutate)} options to true in order to modify argument")
19+
{
20+
21+
}
22+
}
23+
}

src/Simpleflow/FlowOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class FlowOptions : IOptions
1010
/// <summary>
1111
/// Gets or sets AllowArgumentToMutate
1212
/// </summary>
13-
// public bool AllowArgumentToMutate { get; set; } // Implement in next release
13+
public bool AllowArgumentToMutate { get; set; }
1414

1515
/// <summary>
1616
/// Gets or sets AllowFunctions

src/Simpleflow/IOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public interface IOptions
1111
/// <summary>
1212
/// Gets or sets AllowArgumentToMutate
1313
/// </summary>
14-
//bool AllowArgumentToMutate { get; } // Implement in next release Check CacheService related impl
14+
bool AllowArgumentToMutate { get; }
1515

1616
/// <summary>
1717
/// Gets or sets AllowFunctions

src/Simpleflow/ScriptHelperContext.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ public sealed class ScriptHelperContext
1212

1313
private ScriptHelperContext() { }
1414

15-
internal ScriptHelperContext(FlowOutput flowOutput, CancellationToken token)
15+
internal ScriptHelperContext(FlowOutput flowOutput, bool isArgumentMutable, CancellationToken token)
1616
{
1717
_flowOutput = flowOutput;
1818
_token = token;
19+
IsArgumentMutable = isArgumentMutable;
1920
}
2021

21-
22+
public bool IsArgumentMutable { get; }
2223
public bool HasErrors => _flowOutput.Errors.Count > 0;
2324
public bool HasMessages => _flowOutput.Messages.Count > 0;
2425
public bool HasOutputs => _flowOutput.Output.Count > 0;

src/Simpleflow/Services/CompilerService.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace Simpleflow.Services
1515
public class CompilerService : IFlowPipelineService
1616
{
1717
private readonly IFunctionRegister _functionRegister;
18+
private readonly IOptions _options;
1819

1920
/// <summary>
2021
///
@@ -24,6 +25,7 @@ public class CompilerService : IFlowPipelineService
2425
public CompilerService(IFunctionRegister functionRegister, IOptions options = null)
2526
{
2627
_functionRegister = functionRegister ?? throw new ArgumentNullException(nameof(functionRegister));
28+
_options = options;
2729
}
2830

2931
/// <inheritdoc />
@@ -58,25 +60,27 @@ So here we don't need to run again */
5860
next?.Invoke(context);
5961
}
6062

61-
private static void CheckFunctionExecutionPermissions<TArg>(FlowContext<TArg> context, ParserEventPublisher eventPublisher)
63+
private void CheckFunctionExecutionPermissions<TArg>(FlowContext<TArg> context, ParserEventPublisher eventPublisher)
6264
{
6365
eventPublisher.OnVisit = (type, data) =>
6466
{
67+
var options = context.Options ?? _options;
68+
6569
if (type == EventType.VisitFunctionOnAvail
66-
&& context.Options?.DenyFunctions != null)
70+
&& options?.DenyFunctions != null)
6771
{
6872
var functionName = data.ToString();
69-
if (context.Options.DenyFunctions.Contains(functionName, StringComparer.OrdinalIgnoreCase))
73+
if (options.DenyFunctions.Contains(functionName, StringComparer.OrdinalIgnoreCase))
7074
{
7175
throw new AccessDeniedException($"Function '{functionName}' cannot be allowed to run in this context.");
7276
}
7377
}
7478

7579
if (type == EventType.VisitFunctionOnAvail
76-
&& context.Options?.AllowFunctions != null)
80+
&& options?.AllowFunctions != null)
7781
{
7882
var functionName = data.ToString();
79-
if (!context.Options.AllowFunctions.Contains(functionName, StringComparer.OrdinalIgnoreCase))
83+
if (!options.AllowFunctions.Contains(functionName, StringComparer.OrdinalIgnoreCase))
8084
{
8185
throw new AccessDeniedException($"Function '{functionName}' cannot be allowed to run in this context.");
8286
}

src/Simpleflow/Services/ExecutionService.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,43 @@
11
// Copyright (c) navtech.io. All rights reserved.
22
// See License in the project root for license information.
33

4+
using System;
5+
46
namespace Simpleflow.Services
57
{
68
/// <summary>
79
/// A service to run the machine code instructions
810
/// </summary>
911
public class ExecutionService : IFlowPipelineService
1012
{
13+
readonly IOptions _options;
14+
15+
/// <summary>
16+
///
17+
/// </summary>
18+
public ExecutionService()
19+
{
20+
21+
}
22+
23+
/// <summary>
24+
///
25+
/// </summary>
26+
/// <param name="options"></param>
27+
public ExecutionService(IOptions options)
28+
{
29+
_options = options ?? throw new ArgumentNullException(nameof(options));
30+
}
31+
1132
/// <inheritdoc />
1233
public void Run<TArg>(FlowContext<TArg> context, NextPipelineService<TArg> next)
1334
{
1435
// Add trace for debugging
1536
context.Trace?.CreateNewTracePoint(nameof(ExecutionService));
1637

17-
var scriptHelperContext = new ScriptHelperContext(context.Output,
18-
context.Options?.CancellationToken ?? default);
38+
var scriptHelperContext = new ScriptHelperContext(context.Output,
39+
(context.Options?.AllowArgumentToMutate ?? _options?.AllowArgumentToMutate) ?? true,
40+
context.Options?.CancellationToken ?? default);
1941

2042
context.Internals.CompiledScript?.Invoke(context.Argument, context.Output, scriptHelperContext);
2143

0 commit comments

Comments
 (0)