Bug 592644 - ES5 indirect eval. Part 1, add support and tests. r=brendan.

--HG--
extra : rebase_source : 5d07f3e847b2adfb46f30f343af2e06d494012c3
This commit is contained in:
Jason Orendorff 2010-10-07 15:00:09 -05:00
Родитель c4ead6da5c
Коммит 332c179f26
9 изменённых файлов: 104 добавлений и 276 удалений

Просмотреть файл

@ -1008,56 +1008,21 @@ obj_eval(JSContext *cx, uintN argc, Value *vp)
JSStackFrame *caller = js_GetScriptedCaller(cx, NULL);
if (!caller) {
/* Eval code needs to inherit principals from the caller. */
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_BAD_INDIRECT_CALL, js_eval_str);
return JS_FALSE;
}
jsbytecode *callerPC = caller->pc(cx);
bool indirectCall = (callerPC && *callerPC != JSOP_EVAL);
bool directCall = (callerPC && js_GetOpcode(cx, caller->script(), callerPC) == JSOP_EVAL);
/*
* If the callee was originally a cross-compartment wrapper, this should
* be an indirect call.
* If the callee was originally a cross-compartment wrapper, this is an
* indirect call.
*/
if (caller->scopeChain().compartment() != vp[0].toObject().compartment())
indirectCall = true;
/*
* Ban indirect uses of eval (nonglobal.eval = eval; nonglobal.eval(....))
* that attempt to use a non-global object as the scope object.
*
* This ban is a bit silly, since we could just disregard the this-argument
* entirely and comply with ES5, which supports indirect eval. See bug
* 592664.
*/
{
JSObject *obj = ComputeThisFromVp(cx, vp);
if (!obj)
return JS_FALSE;
/*
* This call to JSObject::wrappedObject is safe because the result is
* only used for this check.
*/
obj = obj->wrappedObject(cx);
OBJ_TO_INNER_OBJECT(cx, obj);
if (!obj)
return JS_FALSE;
JSObject *parent = obj->getParent();
if (indirectCall || parent) {
uintN flags = parent
? JSREPORT_ERROR
: JSREPORT_STRICT | JSREPORT_WARNING;
if (!JS_ReportErrorFlagsAndNumber(cx, flags, js_GetErrorMessage, NULL,
JSMSG_BAD_INDIRECT_CALL,
js_eval_str)) {
return JS_FALSE;
}
}
}
if (directCall && caller->scopeChain().compartment() != vp[0].toObject().compartment())
directCall = false;
Value *argv = JS_ARGV(cx, vp);
if (!argv[0].isString()) {
@ -1083,52 +1048,38 @@ obj_eval(JSContext *cx, uintN argc, Value *vp)
MUST_FLOW_THROUGH("out");
uintN staticLevel = caller->script()->staticLevel + 1;
JSObject *scopeobj;
/*
* Bring fp->scopeChain up to date. We're either going to use
* it (direct call) or save it and restore it (indirect call).
* Per ES5, if we see an indirect call, then run in the global scope.
* (eval is specified this way so that the compiler can make assumptions
* about what bindings may or may not exist in the current frame if it
* doesn't see 'eval'.)
*/
JSObject *callerScopeChain;
if (callerPC && *callerPC == JSOP_EVAL)
callerScopeChain = js_GetScopeChainFast(cx, caller, JSOP_EVAL,
if (directCall) {
/* Compile using the caller's current scope object. */
scopeobj = js_GetScopeChainFast(cx, caller, JSOP_EVAL,
JSOP_EVAL_LENGTH + JSOP_LINENO_LENGTH);
else
callerScopeChain = js_GetScopeChain(cx, caller);
if (!callerScopeChain)
if (!scopeobj)
return JS_FALSE;
JSObject *scopeobj = NULL;
#if JS_HAS_EVAL_THIS_SCOPE
/*
* If we see an indirect call, then run eval in the global scope. We do
* this so the compiler can make assumptions about what bindings may or
* may not exist in the current frame if it doesn't see 'eval'.
*/
if (indirectCall) {
JS_ASSERT_IF(caller->isFunctionFrame(), caller->hasCallObj());
} else {
/* Pretend that we're top level. */
staticLevel = 0;
scopeobj = vp[0].toObject().getGlobal();
} else {
/*
* Compile using the caller's current scope object.
*
* NB: This means that the C API must not be used to call eval.
*/
JS_ASSERT_IF(caller->isFunctionFrame(), caller->hasCallObj());
scopeobj = callerScopeChain;
}
#endif
/* Ensure we compile this eval with the right object in the scope chain. */
JSObject *result = CheckScopeChainValidity(cx, scopeobj, js_eval_str);
JS_ASSERT_IF(result, result == scopeobj);
if (!result)
return JS_FALSE;
JS_ASSERT(result == scopeobj);
// CSP check: is eval() allowed at all?
// report errors via CSP is done in the script security mgr.
/*
* CSP check: Is eval() allowed at all?
* Report errors via CSP is done in the script security mgr.
*/
if (!js_CheckContentSecurityPolicy(cx)) {
JS_ReportError(cx, "call to eval() blocked by CSP");
return JS_FALSE;
@ -1174,7 +1125,7 @@ obj_eval(JSContext *cx, uintN argc, Value *vp)
* calls to eval from global code are not cached.
*/
JSScript **bucket = EvalCacheHash(cx, str);
if (!indirectCall && caller->isFunctionFrame()) {
if (directCall && caller->isFunctionFrame()) {
uintN count = 0;
JSScript **scriptp = bucket;

Просмотреть файл

@ -86,7 +86,6 @@
#define JS_HAS_OBJ_PROTO_PROP 0 /* has o.__proto__ etc. */
#endif
#define JS_HAS_OBJ_WATCHPOINT 0 /* has o.watch and o.unwatch */
#define JS_HAS_EVAL_THIS_SCOPE 0 /* Math.eval is same as with (Math) */
#define JS_HAS_SHARP_VARS 0 /* has #n=, #n# for object literals */
#define JS_HAS_XDR 0 /* has XDR API and internal support */
#define JS_HAS_TOSOURCE 0 /* has Object/Array toSource method */
@ -115,7 +114,6 @@
#define JS_HAS_PERL_SUBSTR 1 /* has str.substr */
#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */
#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */
#define JS_HAS_EVAL_THIS_SCOPE 1 /* Math.eval is same as with (Math) */
#define JS_HAS_SHARP_VARS 1 /* has #n=, #n# for object literals */
#define JS_HAS_XDR 1 /* has XDR API and internal support */
#define JS_HAS_TOSOURCE 1 /* has Object/Array toSource method */
@ -140,7 +138,6 @@
#define JS_HAS_PERL_SUBSTR 1 /* has str.substr */
#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */
#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */
#define JS_HAS_EVAL_THIS_SCOPE 1 /* Math.eval is same as with (Math) */
#define JS_HAS_SHARP_VARS 1 /* has #n=, #n# for object literals */
#define JS_HAS_XDR 1 /* has XDR API and internal support */
#define JS_HAS_TOSOURCE 1 /* has Object/Array toSource method */
@ -165,7 +162,6 @@
#define JS_HAS_PERL_SUBSTR 1 /* has str.substr */
#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */
#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */
#define JS_HAS_EVAL_THIS_SCOPE 1 /* Math.eval is same as with (Math) */
#define JS_HAS_SHARP_VARS 1 /* has #n=, #n# for object literals */
#define JS_HAS_XDR 1 /* has XDR API and internal support */
#define JS_HAS_TOSOURCE 1 /* has Object/Array toSource method */
@ -190,7 +186,6 @@
#define JS_HAS_PERL_SUBSTR 1 /* has str.substr */
#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */
#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */
#define JS_HAS_EVAL_THIS_SCOPE 1 /* Math.eval is same as with (Math) */
#define JS_HAS_SHARP_VARS 1 /* has #n=, #n# for object literals */
#define JS_HAS_XDR 1 /* has XDR API and internal support */
#define JS_HAS_TOSOURCE 1 /* has Object/Array toSource method */

Просмотреть файл

@ -0,0 +1,38 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
var a = 9;
var global = this;
function test() {
var a = 0;
// direct eval sees local a
assertEq(eval('a+1'), 1);
assertEq(eval('eval("a+1")'), 1);
// indirect: using a name other than 'eval'
var foo = eval;
assertEq(foo('a+1'), 10);
assertEq(eval('foo("a+1")'), 10); // outer eval is direct, inner foo("a+1") is indirect
// indirect: qualified method call
assertEq(this.eval("a+1"), 10);
assertEq(global.eval("a+1"), 10);
var obj = {foo: eval, eval: eval};
assertEq(obj.foo('a+1'), 10);
assertEq(obj.eval('a+1'), 10);
var name = "eval";
assertEq(obj[name]('a+1'), 10);
assertEq([eval][0]('a+1'), 10);
// indirect: not called from a CallExpression at all
assertEq(eval.call(undefined, 'a+1'), 10);
assertEq(eval.call(global, 'a+1'), 10);
assertEq(eval.apply(undefined, ['a+1']), 10);
assertEq(eval.apply(global, ['a+1']), 10);
assertEq(['a+1'].map(eval)[0], 10);
}
test();
reportCompare(0, 0);

Просмотреть файл

@ -0,0 +1,38 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
var a = 9;
function directArg(eval, s) {
var a = 1;
return eval(s);
}
function directVar(f, s) {
var eval = f;
var a = 1;
return eval(s);
}
function directWith(obj, s) {
var f;
with (obj) {
f = function () {
var a = 1;
return eval(s);
};
}
return f();
}
// direct eval, even though 'eval' is an argument
assertEq(directArg(eval, 'a+1'), 2);
// direct eval, even though 'eval' is a var
assertEq(directVar(eval, 'a+1'), 2);
// direct eval, even though 'eval' is found via a with block
assertEq(directWith(this, 'a+1'), 2);
assertEq(directWith({eval: eval, a: -1000}, 'a+1'), 2);
reportCompare(0, 0);

Просмотреть файл

@ -1,2 +1,4 @@
url-prefix ../../jsreftest.html?test=ecma_5/Global/
script parseInt-01.js
script eval-01.js
script eval-02.js

Просмотреть файл

@ -146,7 +146,6 @@ script regress-380581.js
script regress-380889.js
script regress-381211.js
script regress-381304.js
script regress-382509.js
script regress-384680.js
script regress-385134.js
script regress-385393-02.js

Просмотреть файл

@ -1,117 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is JavaScript Engine testing utilities.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
//-----------------------------------------------------------------------------
var BUGNUMBER = 382509;
var summary = 'Disallow non-global indirect eval';
var actual = '';
var expect = '';
var global = typeof window == 'undefined' ? this : window;
var object = {};
//-----------------------------------------------------------------------------
test();
//-----------------------------------------------------------------------------
function test()
{
enterFunc ('test');
printBugNumber(BUGNUMBER);
printStatus (summary);
if (options().match(/strict/))
{
options('strict');
}
if (options().match(/werror/))
{
options('werror');
}
global.foo = eval;
global.a = 'global';
expect = 'global indirect';
actual = global.foo('a+" indirect"');
reportCompare(expect, actual, summary + ': global indirect');
object.foo = eval;
object.a = 'local';
expect = 'EvalError: function eval must be called directly, and not by way of a function of another name';
try
{
actual = object.foo('a+" indirect"');
}
catch(ex)
{
actual = ex + '';
}
reportCompare(expect, actual, summary + ': local indirect');
options('strict');
options('werror');
try
{
var foo = eval;
print("foo(1+1)" + foo('1+1'));
actual = 'No Error';
}
catch(ex)
{
actual = ex + '';
}
reportCompare(expect, actual, summary + ': strict, rename warning');
options('strict');
options('werror');
expect = 'No Error';
try
{
var foo = eval;
foo('1+1');
actual = 'No Error';
}
catch(ex)
{
actual = ex + '';
}
reportCompare(expect, actual, summary + ': not strict, no rename warning');
exitFunc ('test');
}

Просмотреть файл

@ -11,6 +11,5 @@ script regress-353078.js
script regress-355002.js
script regress-372565.js
script regress-378492.js
script regress-382509.js
script regress-475469.js
script regress-476655.js

Просмотреть файл

@ -1,77 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is JavaScript Engine testing utilities.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
//-----------------------------------------------------------------------------
var BUGNUMBER = 382509;
var summary = 'Disallow non-global indirect eval';
var actual = '';
var expect = '';
var global = typeof window == 'undefined' ? this : window;
var object = {};
//-----------------------------------------------------------------------------
test();
//-----------------------------------------------------------------------------
function test()
{
enterFunc ('test');
printBugNumber(BUGNUMBER);
printStatus (summary);
global.foo = eval;
global.a = 'global';
expect = 'global indirect';
actual = String(['a+" indirect"'].map(global.foo));
reportCompare(expect, actual, summary + ': global indirect');
object.foo = eval;
object.a = 'local';
expect = 'EvalError: function eval must be called directly, and not by way of a function of another name';
try
{
actual = String(['a+" indirect"'].map(object.foo, object));
}
catch(ex)
{
actual = ex + '';
}
reportCompare(expect, actual, summary + ': local indirect');
exitFunc ('test');
}