Skip to content
Merged
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
36 changes: 26 additions & 10 deletions src/main/java/com/dashjoin/jsonata/Functions.java
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ public static List<Map> match(String str, Pattern regex, Integer limit) {
);
}

var result = Utils.createSequence();
var result = createSequence();
var matches = evaluateMatcher(regex, str);
int max = Integer.MAX_VALUE;
if (limit!=null)
Expand Down Expand Up @@ -1591,7 +1591,7 @@ public static List map(List arr, Object func) throws Throwable {
return null;
}

List result = Utils.createSequence();
List result = createSequence();
// do the map - iterate over the arrays, and invoke func
for (int i=0; i<arr.size(); i++) {
Object arg = arr.get(i);
Expand All @@ -1616,7 +1616,7 @@ public static List filter(List arr, Object func) throws Throwable {
return null;
}

var result = Utils.createSequence();
var result = createSequence();

for (var i = 0; i < arr.size(); i++) {
var entry = arr.get(i);
Expand Down Expand Up @@ -1759,7 +1759,7 @@ public static Object foldLeft(List sequence, Object func, Object init) throws Th
* @returns {Array} Array of keys
*/
public static List keys(Object arg) {
var result = Utils.createSequence();
var result = createSequence();

if (arg instanceof List) {
Set keys = new LinkedHashSet();
Expand Down Expand Up @@ -1796,7 +1796,7 @@ public static boolean exists(Object arg) {
* @returns {*} - the array
*/
public static Object spread(Object arg) {
Object result = Utils.createSequence();
Object result = createSequence();

if (arg instanceof List) {
// spread all of the items in the array
Expand Down Expand Up @@ -1868,7 +1868,7 @@ public static List each(Map obj, Object func) throws Throwable {
return null;
}

var result = Utils.createSequence();
var result = createSequence();

for (var key : obj.keySet()) {
var func_args = hofFuncArgs(func, obj.get(key), key, obj);
Expand Down Expand Up @@ -2032,7 +2032,7 @@ public static Object distinct(Object _arr) {
}
List arr = (List)_arr;

var results = (arr instanceof JList/*sequence*/) ? Utils.createSequence() : new ArrayList<>();
var results = (arr instanceof JList/*sequence*/) ? createSequence() : new ArrayList<>();

// Create distinct list of elements by adding all to a set,
// and then adding the set to the result
Expand Down Expand Up @@ -2099,7 +2099,7 @@ public static Object append(Object arg1, Object arg2) {

// if either argument is not an array, make it so
if (!(arg1 instanceof List)) {
arg1 = Utils.createSequence(arg1);
arg1 = createSequence(arg1);
}
if (!(arg2 instanceof List)) {
arg2 = new JList<>(Arrays.asList(arg2));
Expand Down Expand Up @@ -2129,7 +2129,7 @@ public static Object lookup(Object input, String key) {
Object result = null;
if (input instanceof List) {
List _input = (List)input;
result = Utils.createSequence();
result = createSequence();
for(var ii = 0; ii < _input.size(); ii++) {
var res = lookup(_input.get(ii), key);
if (res != null) {
Expand Down Expand Up @@ -2392,7 +2392,7 @@ public static Object functionEval(String expr, Object focus) {
input = focus;
// if the input is a JSON array, then wrap it in a singleton sequence so it gets treated as a single input
if((input instanceof List) && !Utils.isSequence(input)) {
input = Utils.createSequence(input);
input = createSequence(input);
((JList)input).outerWrapper = true;
}
}
Expand Down Expand Up @@ -2435,4 +2435,20 @@ public static long millis() {
long t = Jsonata.current.get().timestamp;
return t;
}

/**
* Creates a new sequence with guardrails of the current environment
* @return
*/
public static List<Object> createSequence() {
return Jsonata.current.get().environment.base.createSequence();
}

/**
* Creates a new sequence with guardrails of the current environment
* @return
*/
public static List<Object> createSequence(Object el) {
return Jsonata.current.get().environment.base.createSequence(el);
}
}
105 changes: 87 additions & 18 deletions src/main/java/com/dashjoin/jsonata/Jsonata.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ public static class Frame {

public Frame(Frame enclosingEnvironment) {
parent = enclosingEnvironment;
if (enclosingEnvironment!=null) {
base = enclosingEnvironment.base;
}
}

public void bind(String name, Object val) {
Expand Down Expand Up @@ -115,6 +118,33 @@ public void setEvaluateEntryCallback(EntryCallback cb) {
public void setEvaluateExitCallback(ExitCallback cb) {
bind("__evaluate_exit", cb);
}

// Guardrails extensions
public Options options;

Frame base;

public int depth;

public long time;

public List<Object> createSequence() { return createSequence(Utils.NONE); }

public List<Object> createSequence(Object el) {
JList<Object> seq = (JList<Object>) Utils.__createSequence(el);
if (options!=null && options.sequence!=null)
seq.setMaxSize(options.sequence);
return seq;
}

public void guardrails() {
if (options!=null && options.stack!=null && depth>options.stack) {
throw new JException("D1011", -1, options.stack);
}
if (options!=null && options.timeout!=null && System.currentTimeMillis()-time > options.timeout) {
throw new JException("D1012", -1, options.timeout);
}
}
}

static Frame staticFrame;// = createFrame(null);
Expand Down Expand Up @@ -152,6 +182,8 @@ Object _evaluate(Symbol expr, Object input, Frame environment) {

if (parser.dbg) System.out.println("eval expr="+expr+" type="+expr.type);//+" input="+input);

environment.base.depth++;
environment.base.guardrails();
var entryCallback = environment.lookup("__evaluate_entry");
if(entryCallback!=null) {
((EntryCallback)entryCallback).callback(expr, input, environment);
Expand Down Expand Up @@ -245,6 +277,8 @@ Object _evaluate(Symbol expr, Object input, Frame environment) {
}
}

environment.base.depth--;

return result;
}

Expand All @@ -264,7 +298,7 @@ Object _evaluate(Symbol expr, Object input, Frame environment) {
inputSequence = (List)input;
} else {
// if input is not an array, make it so
inputSequence = Utils.createSequence(input);
inputSequence = environment.base.createSequence(input);
}

Object resultSequence = null;
Expand Down Expand Up @@ -305,7 +339,7 @@ Object _evaluate(Symbol expr, Object input, Frame environment) {
// tuple stream is carrying ancestry information - keep this
resultSequence = tupleBindings;
} else {
resultSequence = Utils.createSequence();
resultSequence = environment.base.createSequence();
for (int ii = 0; ii < tupleBindings.size(); ii++) {
((List)resultSequence).add(tupleBindings.get(ii).get("@"));
}
Expand All @@ -320,7 +354,7 @@ Object _evaluate(Symbol expr, Object input, Frame environment) {

// if the array is explicitly constructed in the expression and marked to promote singleton sequences to array
if((resultSequence instanceof JList) && ((JList)resultSequence).cons && !((JList)resultSequence).sequence) {
resultSequence = Utils.createSequence(resultSequence);
resultSequence = environment.base.createSequence(resultSequence);
}
((JList)resultSequence).keepSingleton = true;
}
Expand Down Expand Up @@ -358,7 +392,7 @@ Frame createFrameFromTuple(Frame environment, Map<String, Object> tuple) {
return result;
}

result = Utils.createSequence();
result = environment.base.createSequence();

for(var ii = 0; ii < ((List)input).size(); ii++) {
var res = /* await */ evaluate(expr, ((List)input).get(ii), environment);
Expand All @@ -372,7 +406,7 @@ Frame createFrameFromTuple(Frame environment, Map<String, Object> tuple) {
}
}

var resultSequence = Utils.createSequence();
var resultSequence = environment.base.createSequence();
if(lastStep && ((List)result).size()==1 && (((List)result).get(0) instanceof List) && !Utils.isSequence(((List)result).get(0))) {
resultSequence = (List) ((List) result).get(0);
} else {
Expand Down Expand Up @@ -425,7 +459,7 @@ Frame createFrameFromTuple(Frame environment, Map<String, Object> tuple) {
result = (List) /* await */ evaluateSortExpression(expr, tupleBindings, environment);
} else {
List sorted = (List) /* await */ evaluateSortExpression(expr, input, environment);
result = Utils.createSequence();
result = environment.base.createSequence();
((JList)result).tupleStream = true;
for(var ss = 0; ss < ((List)sorted).size(); ss++) {
var tuple = Map.of("@", sorted.get(ss),
Expand All @@ -439,7 +473,7 @@ Frame createFrameFromTuple(Frame environment, Map<String, Object> tuple) {
return result;
}

result = Utils.createSequence();
result = environment.base.createSequence();
((JList)result).tupleStream = true;
var stepEnv = environment;
if(tupleBindings == null) {
Expand Down Expand Up @@ -498,12 +532,12 @@ Frame createFrameFromTuple(Frame environment, Map<String, Object> tuple) {
*/
/* async */ Object evaluateFilter(Object _predicate, Object input, Frame environment) {
Symbol predicate = (Symbol)_predicate;
var results = Utils.createSequence();
var results = environment.base.createSequence();
if( input instanceof JList && ((JList)input).tupleStream) {
((JList)results).tupleStream = true;
}
if (!(input instanceof List)) { // isArray
input = Utils.createSequence(input == null ? Utils.NONE : input);
input = environment.base.createSequence(input == null ? Utils.NONE : input);
}
if (predicate.type.equals("number")) {
var index = ((Number)predicate.value).intValue(); // round it down - was Math.floor
Expand Down Expand Up @@ -534,7 +568,7 @@ Frame createFrameFromTuple(Frame environment, Map<String, Object> tuple) {
}
var res = /* await */ evaluate(predicate, context, env);
if (Utils.isNumeric(res)) {
res = Utils.createSequence(res);
res = environment.base.createSequence(res);
}
if (Utils.isArrayOfNumbers(res)) {
for (Object ires : ((List)res)) {
Expand Down Expand Up @@ -717,7 +751,7 @@ Object evaluateLiteral(Symbol expr) {
* @returns {*} Evaluated input data
*/
Object evaluateWildcard(Symbol expr, Object input) {
var results = Utils.createSequence();
var results = environment.base.createSequence();
if ((input instanceof JList) && ((JList)input).outerWrapper && ((JList)input).size() > 0) {
input = ((JList)input).get(0);
}
Expand Down Expand Up @@ -776,7 +810,7 @@ Object flatten(Object arg, List flattened) {
*/
Object evaluateDescendants(Symbol expr, Object input) {
Object result = null;
var resultSequence = Utils.createSequence();
var resultSequence = environment.base.createSequence();
if (input != null) {
// traverse all descendants of this object/array
recurseDescendants(input, resultSequence);
Expand Down Expand Up @@ -1066,7 +1100,7 @@ static class GroupEntry {
var reduce = (_input instanceof JList) && ((JList)_input).tupleStream ? true : false;
// group the input sequence by "key" expression
if (!(_input instanceof List)) {
_input = Utils.createSequence(_input);
_input = environment.base.createSequence(_input);
}
List input = (List)_input;

Expand Down Expand Up @@ -1218,7 +1252,12 @@ Object evaluateRangeExpression(Object lhs, Object rhs) {
size
);
}

if(environment.base.options!=null && size > environment.base.options.sequence) {
throw new JException("D2015",
-1,
size
);
}
return new Utils.RangeList(_lhs, _rhs);
}

Expand Down Expand Up @@ -2403,6 +2442,8 @@ static void registerFunctions() {
put("T1008", "Attempted to partially apply a non-function");
put("D1009", "Multiple key definitions evaluate to same key: {{value}}");
put("T1010", "The matcher Object argument passed to Object {{token}} does not return the correct object structure");
put("D1011", "Stack overflow. Check for non-terminating recursive function. Consider rewriting as tail-recursive");
put("D1012", "Evaluation timeout after {{value}} milliseconds. Check for infinite loop");
put("T2001", "The left side of the {{token}} operator must evaluate to a number");
put("T2002", "The right side of the {{token}} operator must evaluate to a number");
put("T2003", "The left side of the range operator (..) must evaluate to an integer");
Expand All @@ -2416,7 +2457,8 @@ static void registerFunctions() {
put("T2011", "The insert/update clause of the transform expression must evaluate to an object: {{value}}");
put("T2012", "The delete clause of the transform expression must evaluate to a string or array of strings: {{value}}");
put("T2013", "The transform expression clones the input object using the $clone() function. This has been overridden in the current scope by a non-function.");
put("D2014", "The size of the sequence allocated by the range operator (..) must not exceed 1e6. Attempted to allocate {{value}}.");
put("D2014", "The size of the sequence allocated by the range operator (..) must not exceed 1e7. Attempted to allocate {{value}}.");
put("D2015", "The maximum sequence length of {{value}} was exceeded.");
put("D2016", "The size of the string requested by the $pad function must not exceed 1e7. Attempted to allocate {{value}}.");
put("D3001", "Attempting to invoke string Object on Infinity or NaN");
put("D3010", "Second argument of replace Object cannot be an empty string");
Expand Down Expand Up @@ -2504,14 +2546,35 @@ Exception populateMessage(Exception err) {
* @throws JException An exception if an error occured.
*/
public static Jsonata jsonata(String expression) {
return new Jsonata(expression);
return new Jsonata(expression, null);
}
public static Jsonata jsonata(String expression, Options options) {
return new Jsonata(expression, options);
}

public static class Options {
public Long timeout;
public Integer stack;
public Integer sequence;

public static OptionsBuilder builder() { return new OptionsBuilder(); }
public static class OptionsBuilder {
private Options options = new Options();
public OptionsBuilder timeout(Long timeout) { options.timeout = timeout; return this; }
public OptionsBuilder stack(Integer stack) { options.stack = stack; return this; }
public OptionsBuilder sequence(Integer sequence) { options.sequence = sequence; return this; }
public Options build() { Options ret = options; options=null; return ret; }
}
}

Options options;

/**
* Internal constructor
* @param expr
*/
Jsonata(String expr) { // boolean optionsRecover) {
Jsonata(String expr, Options options) { // boolean optionsRecover) {
this.options = options;
try {
ast = parser.parse(expr);//, optionsRecover);
errors = ast.errors;
Expand Down Expand Up @@ -2635,13 +2698,19 @@ public Object evaluate(Object input, Frame bindings) { // FIXME:, callback) {
// the $now() and $millis() functions will return this value - whenever it is called
timestamp = System.currentTimeMillis();
//exec_env.timestamp = timestamp;
exec_env.time = timestamp;
exec_env.options = options;

// if the input is a JSON array, then wrap it in a singleton sequence so it gets treated as a single input
if((input instanceof List) && !Utils.isSequence(input)) {
input = Utils.createSequence(input);
input = exec_env.createSequence(input);
((JList)input).outerWrapper = true;
}

// Porting JS to Java note: guardrails function is in Java class Frame
exec_env.base = exec_env;
exec_env.depth = 0;

if (validateInput)
Functions.validateInput(input);

Expand Down
Loading
Loading