Statement injection in C#
We have a system that allows a user to write a single statement to be evaluated with a subset of C#: something like
using System;
namespace Evaluation {
public class _Evaluator {
public {0} {1}() {
return ({2});
}
}
}
and then returning (new _Evaluator()).{1}()
, where {0}
is constrained to be an int
, double
or string
.
The statement takes inputs $1
, $2
, $3
, etc. Often, it’s used to calculate something simple:
$1
$1 + $2
$1 >= 1.0 ? $1 : "LOD"
Sometimes, the user wants something more complicated. We have a few custom functions like Sum
, Avg
and Min
/Max
, and we can call out to some C# functions like Math.Round
. We can also deploy new functions, which is the ‘right’ way to solve these problems, but the product in question is legacy and new deployments are difficult and preferably avoided.
A recent complicated example is “sum all of these variables, provided they are greater than 1”. If there are a small number of variables, you can do this with a stacked ternary:
$1 > 1 && $2 > 1 ? $1 + $2 : $1 > 1 ? $1 : $2 > $1 ? $2 : 0
or
$1 > 1 ? $2 > 1 ? $1 + $2 : $1 : $2 > 1 ? $2 : 0
but this is unreadable and very difficult to write for larger numbers of variables.
Preferably, we’d use LINQ:
(new double[] {$1, $2, $3}).Where(x => x >= 1.0).Sum()
but this requires a using System.Linq
that the existing deployment didn’t have. I tried using the full System.Linq.Where
, but couldn’t get that to compile. I wanted to add a using
inside the function, but you can’t add a using
after a namespace, or create a new namespace, so this is a no-go.
My next thought was from JavaScript: could I create an IIFE-like anonymous function, giving me access to expressions instead of statements, and call it immediately?
() => {
double sum = 0;
foreach (var item in new double[] {$1, $2, $3}) {
if (item >= 1.0) sum += item;
}
return sum;
}()
Unfortunately that doesn’t work, giving the error CS0149: Method name expected
.
Fortunately, the fix is quite simple: wrap the lambda in a new Func
:
new Func<double>(() => [...])()
This let us compute a “small sum” of 17 variables without having to do a new deployment or write an unreadable ternary, which was much appreciated!