Skip to content

Commit

Permalink
Add mode to throw on invalid property access, fixes mozilla#25
Browse files Browse the repository at this point in the history
  • Loading branch information
dmose committed Jul 11, 2022
1 parent 2f6cc44 commit 15ead6a
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 17 deletions.
10 changes: 8 additions & 2 deletions lib/Jexl.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ var Evaluator = require("./evaluator/Evaluator"),
* xpath-like drilldown into native Javascript objects.
* @constructor
*/
function Jexl() {
function Jexl(throwOnMissingProp) {
this._customGrammar = null;
this._lexer = null;
this._transforms = {};
this._throwOnMissingProp = throwOnMissingProp || true;
}

/**
Expand Down Expand Up @@ -174,7 +175,12 @@ Jexl.prototype._eval = function(exp, context) {
var self = this,
grammar = this._getGrammar(),
parser = new Parser(grammar),
evaluator = new Evaluator(grammar, this._transforms, context);
evaluator = new Evaluator(
grammar,
this._transforms,
context,
this._throwOnMissingProp
);
return Promise.resolve().then(function() {
parser.addTokens(self._getLexer().tokenize(exp));
return evaluator.eval(parser.complete());
Expand Down
18 changes: 16 additions & 2 deletions lib/evaluator/Evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,20 @@ var handlers = require("./handlers");
* to resolve the value of a relative identifier.
* @constructor
*/
var Evaluator = function(grammar, transforms, context, relativeContext) {
var Evaluator = function(
grammar,
transforms,
context,
relativeContext,
throwOnMissingProp
) {
this._grammar = grammar;
this._transforms = transforms || {};
this._context = context || {};
this._relContext = relativeContext || this._context;
this._throwOnMissingProp = true;
throwOnMissingProp || false;
console.log("_tOMP", this._throwOnMissingProp);
};

/**
Expand All @@ -50,7 +59,12 @@ var Evaluator = function(grammar, transforms, context, relativeContext) {
Evaluator.prototype.eval = function(ast) {
var self = this;
return Promise.resolve().then(function() {
return handlers[ast.type].call(self, ast);
try {
var retVal = handlers[ast.type].call(self, ast);
} catch (ex) {
console.log("handler threw: ", ex);
}
return retVal;
});
};

Expand Down
23 changes: 19 additions & 4 deletions lib/evaluator/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,31 @@ exports.FilterExpression = function(ast) {
* @private
*/
exports.Identifier = function(ast) {
let throwOnMissingProp = true; // this._throwOnMissingProp;

if (ast.from) {
return this.eval(ast.from).then(function(context) {
if (Array.isArray(context)) context = context[0];
if (context === undefined) return undefined;
if (context === undefined) return undefined; // XXX deleteme? testme?
if (throwOnMissingProp && !(ast.value in context)) {
throw new Error(
`stemmed context does not have an identifier named ${ast.value}`
);
}

return context[ast.value];
});
} else {
return ast.relative
? this._relContext[ast.value]
: this._context[ast.value];
const contextToCheck = ast.relative ? this._relContext : this._context;

console.log("b4");
if (throwOnMissingProp && !(ast.value in contextToCheck)) {
throw new Error(
`default context does not have an identifier named ${ast.value}`
);
}

return contextToCheck[ast.value];
}
};

Expand Down
14 changes: 13 additions & 1 deletion test/evaluator/Evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,19 @@ describe("Evaluator", function() {
});
it("should throw when transform does not exist", function() {
var e = new Evaluator(grammar);
return e.eval(toTree('"hello"|world')).should.reject;
return e.eval(toTree('"hello"|world')).should.be.rejected;
});
it("should throw when top-level identifier doesn't exist in throw mode", function() {
console.log("about to pass in true");
var context = { foo: { baz: { bar: "dog" } } },
e = new Evaluator(grammar, null, context, null, true);
return e.eval(toTree("monkey")).should.be.rejected;
});
it("should throw when child identifier doesn't exist in throw mode", function() {
console.log("about to pass in true");
var context = { foo: { baz: { bar: "cat" } } },
e = new Evaluator(grammar, null, context, null, true);
return e.eval(toTree("foo.baz.monkey")).should.be.rejected;
});
it("should apply the DivFloor operator", function() {
var e = new Evaluator(grammar);
Expand Down
51 changes: 43 additions & 8 deletions vendor/mozjexl.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,11 @@ var Evaluator = __webpack_require__(2),
* xpath-like drilldown into native Javascript objects.
* @constructor
*/
function Jexl() {
function Jexl(throwOnMissingProp) {
this._customGrammar = null;
this._lexer = null;
this._transforms = {};
this._throwOnMissingProp = throwOnMissingProp || true;
}

/**
Expand Down Expand Up @@ -460,7 +461,12 @@ Jexl.prototype._eval = function(exp, context) {
var self = this,
grammar = this._getGrammar(),
parser = new Parser(grammar),
evaluator = new Evaluator(grammar, this._transforms, context);
evaluator = new Evaluator(
grammar,
this._transforms,
context,
this._throwOnMissingProp
);
return Promise.resolve().then(function() {
parser.addTokens(self._getLexer().tokenize(exp));
return evaluator.eval(parser.complete());
Expand Down Expand Up @@ -552,11 +558,20 @@ var handlers = __webpack_require__(3);
* to resolve the value of a relative identifier.
* @constructor
*/
var Evaluator = function(grammar, transforms, context, relativeContext) {
var Evaluator = function(
grammar,
transforms,
context,
relativeContext,
throwOnMissingProp
) {
this._grammar = grammar;
this._transforms = transforms || {};
this._context = context || {};
this._relContext = relativeContext || this._context;
this._throwOnMissingProp = true;
throwOnMissingProp || false;
console.log("_tOMP", this._throwOnMissingProp);
};

/**
Expand All @@ -567,7 +582,12 @@ var Evaluator = function(grammar, transforms, context, relativeContext) {
Evaluator.prototype.eval = function(ast) {
var self = this;
return Promise.resolve().then(function() {
return handlers[ast.type].call(self, ast);
try {
var retVal = handlers[ast.type].call(self, ast);
} catch (ex) {
console.log("handler threw: ", ex);
}
return retVal;
});
};

Expand Down Expand Up @@ -762,16 +782,31 @@ exports.FilterExpression = function(ast) {
* @private
*/
exports.Identifier = function(ast) {
let throwOnMissingProp = true; // this._throwOnMissingProp;

if (ast.from) {
return this.eval(ast.from).then(function(context) {
if (Array.isArray(context)) context = context[0];
if (context === undefined) return undefined;
if (context === undefined) return undefined; // XXX deleteme? testme?
if (throwOnMissingProp && !(ast.value in context)) {
throw new Error(
`stemmed context does not have an identifier named ${ast.value}`
);
}

return context[ast.value];
});
} else {
return ast.relative
? this._relContext[ast.value]
: this._context[ast.value];
const contextToCheck = ast.relative ? this._relContext : this._context;

console.log("b4");
if (throwOnMissingProp && !(ast.value in contextToCheck)) {
throw new Error(
`default context does not have an identifier named ${ast.value}`
);
}

return contextToCheck[ast.value];
}
};

Expand Down

0 comments on commit 15ead6a

Please sign in to comment.