Commit 3877c91a authored by olehougaard's avatar olehougaard

Fixing the detection of aliased eval so that it is exact.

Fixing the semantics of aliased eval so that it is conformant.
Review URL: http://codereview.chromium.org/11563

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@819 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent ab077304
......@@ -37,7 +37,7 @@ VariableProxySentinel VariableProxySentinel::this_proxy_(true);
VariableProxySentinel VariableProxySentinel::identifier_proxy_(false);
ValidLeftHandSideSentinel ValidLeftHandSideSentinel::instance_;
Property Property::this_property_(VariableProxySentinel::this_proxy(), NULL, 0);
Call Call::sentinel_(NULL, NULL, false, 0);
Call Call::sentinel_(NULL, NULL, Call::ALIASED, 0);
// ----------------------------------------------------------------------------
......
......@@ -864,13 +864,20 @@ class Property: public Expression {
class Call: public Expression {
public:
enum EvalType {
ALIASED, // Either not eval or an aliased eval.
POTENTIALLY_DIRECT // Looks like a direct eval at codegen time.
// Needs to be determined at runtime whether the
// eval is direct.
};
Call(Expression* expression,
ZoneList<Expression*>* arguments,
bool is_eval,
EvalType eval_type,
int pos)
: expression_(expression),
arguments_(arguments),
is_eval_(is_eval),
eval_type_(eval_type),
pos_(pos) { }
virtual void Accept(Visitor* v);
......@@ -880,7 +887,7 @@ class Call: public Expression {
Expression* expression() const { return expression_; }
ZoneList<Expression*>* arguments() const { return arguments_; }
bool is_eval() { return is_eval_; }
EvalType eval_type() { return eval_type_; }
int position() { return pos_; }
static Call* sentinel() { return &sentinel_; }
......@@ -888,7 +895,7 @@ class Call: public Expression {
private:
Expression* expression_;
ZoneList<Expression*>* arguments_;
bool is_eval_;
EvalType eval_type_;
int pos_;
static Call sentinel_;
......@@ -898,7 +905,7 @@ class Call: public Expression {
class CallNew: public Call {
public:
CallNew(Expression* expression, ZoneList<Expression*>* arguments, int pos)
: Call(expression, arguments, false, pos) { }
: Call(expression, arguments, ALIASED, pos) { }
virtual void Accept(Visitor* v);
};
......
......@@ -2320,6 +2320,12 @@ void CodeGenerator::VisitCall(Call* node) {
// is resolved in cache misses (this also holds for megamorphic calls).
// ------------------------------------------------------------------------
if (node->eval_type() == Call::POTENTIALLY_DIRECT) {
__ CallRuntime(Runtime::kSetInPotentiallyDirectEval, 0);
} else {
__ CallRuntime(Runtime::kClearInPotentiallyDirectEval, 0);
}
if (var != NULL && !var->is_this() && var->is_global()) {
// ----------------------------------
// JavaScript example: 'foo(1, 2, 3)' // foo is global
......
......@@ -2690,6 +2690,12 @@ void CodeGenerator::VisitCall(Call* node) {
// is resolved in cache misses (this also holds for megamorphic calls).
// ------------------------------------------------------------------------
if (node->eval_type() == Call::POTENTIALLY_DIRECT) {
__ CallRuntime(Runtime::kSetInPotentiallyDirectEval, 0);
} else {
__ CallRuntime(Runtime::kClearInPotentiallyDirectEval, 0);
}
if (var != NULL && !var->is_this() && var->is_global()) {
// ----------------------------------
// JavaScript example: 'foo(1, 2, 3)' // foo is global
......
......@@ -1117,7 +1117,7 @@ DebugCommandProcessor.prototype.scriptsCommandToJSONRequest_ = function(args) {
DebugCommandProcessor.prototype.responseToText = function(json_response) {
try {
// Convert the JSON string to an object.
response = %CompileString('(' + json_response + ')', 0, false)();
response = %CompileString('(' + json_response + ')', 0)();
if (!response.success) {
return response.message;
......@@ -1323,7 +1323,7 @@ DebugCommandProcessor.prototype.processDebugJSONRequest = function(json_request,
try {
try {
// Convert the JSON string to an object.
request = %CompileString('(' + json_request + ')', 0, false)();
request = %CompileString('(' + json_request + ')', 0)();
// Create an initial response.
response = this.createResponse(request);
......@@ -1776,7 +1776,7 @@ DebugCommandProcessor.prototype.scriptsRequest_ = function(request, response) {
DebugCommandProcessor.prototype.isRunning = function(json_response) {
try {
// Convert the JSON string to an object.
response = %CompileString('(' + json_response + ')', 0, false)();
response = %CompileString('(' + json_response + ')', 0)();
// Return whether VM should be running after this request.
return response.running;
......
......@@ -327,6 +327,8 @@ class ParserFactory BASE_EMBEDDED {
return Handle<String>();
}
virtual Expression* NewProperty(Expression* obj, Expression* key, int pos) {
if (obj == VariableProxySentinel::this_proxy()) {
return Property::this_property();
......@@ -337,7 +339,7 @@ class ParserFactory BASE_EMBEDDED {
virtual Expression* NewCall(Expression* expression,
ZoneList<Expression*>* arguments,
bool is_eval, int pos) {
Call::EvalType eval_type, int pos) {
return Call::sentinel();
}
......@@ -387,8 +389,8 @@ class AstBuildingParserFactory : public ParserFactory {
virtual Expression* NewCall(Expression* expression,
ZoneList<Expression*>* arguments,
bool is_eval, int pos) {
return new Call(expression, arguments, is_eval, pos);
Call::EvalType eval_type, int pos) {
return new Call(expression, arguments, eval_type, pos);
}
virtual Statement* EmptyStatement() {
......@@ -2294,38 +2296,32 @@ Expression* Parser::ParseLeftHandSideExpression(bool* ok) {
ZoneList<Expression*>* args = ParseArguments(CHECK_OK);
// Keep track of eval() calls since they disable all local variable
// optimizations. We can ignore locally declared variables with
// name 'eval' since they override the global 'eval' function. We
// only need to look at unresolved variables (VariableProxies).
// optimizations.
// The calls that need special treatment are the
// direct (i.e. not aliased) eval calls. These calls are all of the
// form eval(...) with no explicit receiver object where eval is not
// declared in the current scope chain. These calls are marked as
// potentially direct eval calls. Whether they are actually direct calls
// to eval is determined at run time.
Call::EvalType eval_type = Call::ALIASED;
if (!is_pre_parsing_) {
// We assume that only a function called 'eval' can be used
// to invoke the global eval() implementation. This permits
// for massive optimizations.
VariableProxy* callee = result->AsVariableProxy();
if (callee != NULL && callee->IsVariable(Factory::eval_symbol())) {
// We do not allow direct calls to 'eval' in our internal
// JS files. Use builtin functions instead.
ASSERT(!Bootstrapper::IsActive());
top_scope_->RecordEvalCall();
} else {
// This is rather convoluted code to check if we're calling
// a function named 'eval' through a property access. If so,
// we mark it as a possible eval call (we don't know if the
// receiver will resolve to the global object or not), but
// we do not treat the call as an eval() call - we let the
// call get through to the JavaScript eval code defined in
// v8natives.js.
Property* property = result->AsProperty();
if (property != NULL) {
Literal* key = property->key()->AsLiteral();
if (key != NULL &&
key->handle().is_identical_to(Factory::eval_symbol())) {
// We do not allow direct calls to 'eval' in our
// internal JS files. Use builtin functions instead.
ASSERT(!Bootstrapper::IsActive());
top_scope_->RecordEvalCall();
}
Handle<String> name = callee->name();
Variable* var = NULL;
for (Scope* scope = top_scope_;
scope != NULL;
scope = scope->outer_scope()) {
var = scope->Lookup(callee->name());
if (var != NULL) break;
}
if (var == NULL) {
// We do not allow direct calls to 'eval' in our internal
// JS files. Use builtin functions instead.
ASSERT(!Bootstrapper::IsActive());
top_scope_->RecordEvalCall();
eval_type = Call::POTENTIALLY_DIRECT;
}
}
}
......@@ -2342,7 +2338,7 @@ Expression* Parser::ParseLeftHandSideExpression(bool* ok) {
if (is_eval && args->length() == 0) {
result = NEW(Literal(Factory::undefined_value()));
} else {
result = factory()->NewCall(result, args, is_eval, pos);
result = factory()->NewCall(result, args, eval_type, pos);
}
break;
}
......
......@@ -3930,95 +3930,107 @@ static Object* Runtime_NumberIsFinite(Arguments args) {
}
static Object* EvalContext() {
// The topmost JS frame belongs to the eval function which called
// the CompileString runtime function. We need to unwind one level
// to get to the caller of eval.
StackFrameLocator locator;
JavaScriptFrame* frame = locator.FindJavaScriptFrame(1);
// TODO(900055): Right now we check if the caller of eval() supports
// eval to determine if it's an aliased eval or not. This may not be
// entirely correct in the unlikely case where a function uses both
// aliased and direct eval calls.
HandleScope scope;
if (!ScopeInfo<>::SupportsEval(frame->FindCode())) {
// Aliased eval: Evaluate in the global context of the eval
// function to support aliased, cross environment evals.
return *Top::global_context();
}
static Object* Runtime_GlobalReceiver(Arguments args) {
ASSERT(args.length() == 1);
Object* global = args[0];
if (!global->IsJSGlobalObject()) return Heap::null_value();
return JSGlobalObject::cast(global)->global_receiver();
}
// Fetch the caller context from the frame.
Handle<Context> caller(Context::cast(frame->context()));
// Check for eval() invocations that cross environments. Use the
// context from the stack if evaluating in current environment.
Handle<Context> target = Top::global_context();
if (caller->global_context() == *target) return *caller;
static Object* Runtime_CompileString(Arguments args) {
HandleScope scope;
ASSERT(args.length() == 2);
CONVERT_ARG_CHECKED(String, source, 0);
CONVERT_ARG_CHECKED(Smi, line_offset, 1);
// Otherwise, use the global context from the other environment.
return *target;
// Compile source string.
Handle<JSFunction> boilerplate =
Compiler::CompileEval(source, line_offset->value(), true);
if (boilerplate.is_null()) return Failure::Exception();
Handle<Context> context(Top::context()->global_context());
Handle<JSFunction> fun =
Factory::NewFunctionFromBoilerplate(boilerplate, context);
return *fun;
}
static Object* Runtime_EvalReceiver(Arguments args) {
static Object* Runtime_ExecDirectEval(Arguments args) {
ASSERT(args.length() == 1);
if (!args[0]->IsString()) return args[0];
Handle<String> source = args.at<String>(0);
// Compute the eval context.
HandleScope scope;
StackFrameLocator locator;
JavaScriptFrame* frame = locator.FindJavaScriptFrame(1);
// Fetch the caller context from the frame.
Context* caller = Context::cast(frame->context());
Handle<Context> context(Context::cast(frame->context()));
bool is_global = context->IsGlobalContext();
// Check for eval() invocations that cross environments. Use the
// top frames receiver if evaluating in current environment.
Context* global_context = Top::context()->global()->global_context();
if (caller->global_context() == global_context) {
return frame->receiver();
// Compile source string.
Handle<JSFunction> boilerplate =
Compiler::CompileEval(source, 0, is_global);
if (boilerplate.is_null()) return Failure::Exception();
Handle<JSFunction> fun =
Factory::NewFunctionFromBoilerplate(boilerplate, context);
// Call generated function
Handle<Object> receiver(frame->receiver());
bool has_pending_exception = false;
Handle<Object> result =
Execution::Call(fun, receiver, 0, NULL, &has_pending_exception);
if (has_pending_exception) {
ASSERT(Top::has_pending_exception());
return Failure::Exception();
}
return *result;
}
// Otherwise use the given argument (the global object of the
// receiving context).
return args[0];
static Object* Runtime_SetInPotentiallyDirectEval(Arguments args) {
ASSERT(args.length() == 0);
Top::set_in_potentially_direct_eval(true);
return Heap::undefined_value();
}
static Object* Runtime_GlobalReceiver(Arguments args) {
ASSERT(args.length() == 1);
Object* global = args[0];
if (!global->IsJSGlobalObject()) return Heap::null_value();
return JSGlobalObject::cast(global)->global_receiver();
static Object* Runtime_ClearInPotentiallyDirectEval(Arguments args) {
ASSERT(args.length() == 0);
Top::set_in_potentially_direct_eval(false);
return Heap::undefined_value();
}
static Object* Runtime_CompileString(Arguments args) {
HandleScope scope;
ASSERT(args.length() == 3);
CONVERT_ARG_CHECKED(String, source, 0);
CONVERT_ARG_CHECKED(Smi, line_offset, 1);
bool contextual = args[2]->IsTrue();
RUNTIME_ASSERT(contextual || args[2]->IsFalse());
static Object* Runtime_InDirectEval(Arguments args) {
ASSERT(args.length() == 0);
// Compute the eval context.
Handle<Context> context;
if (contextual) {
// Get eval context. May not be available if we are calling eval
// through an alias, and the corresponding frame doesn't have a
// proper eval context set up.
Object* eval_context = EvalContext();
if (eval_context->IsFailure()) return eval_context;
context = Handle<Context>(Context::cast(eval_context));
} else {
context = Handle<Context>(Top::context()->global_context());
// No need to look further if the static analysis showed that this
// is aliased.
if (!Top::is_in_potentially_direct_eval()) {
return Heap::false_value();
}
// Compile source string.
bool is_global = context->IsGlobalContext();
Handle<JSFunction> boilerplate =
Compiler::CompileEval(source, line_offset->value(), is_global);
if (boilerplate.is_null()) return Failure::Exception();
Handle<JSFunction> fun =
Factory::NewFunctionFromBoilerplate(boilerplate, context);
return *fun;
// Find where the 'eval' symbol is bound. It is unaliased only if
// it is bound in the global object.
HandleScope scope;
StackFrameLocator locator;
JavaScriptFrame* frame = locator.FindJavaScriptFrame(1);
Context* context = Context::cast(frame->context());
int index;
PropertyAttributes attributes;
while (context && !context->IsGlobalContext()) {
context->Lookup(Factory::eval_symbol(), FOLLOW_PROTOTYPE_CHAIN,
&index, &attributes);
if (attributes != ABSENT) return Heap::false_value();
if (context->is_function_context()) {
context = Context::cast(context->closure()->context());
} else {
context = context->previous();
}
}
return Heap::true_value();
}
......
......@@ -190,13 +190,16 @@ namespace v8 { namespace internal {
F(NumberIsFinite, 1) \
\
/* Globals */ \
F(CompileString, 3) \
F(CompileString, 2) \
F(CompileScript, 4) \
F(GlobalPrint, 1) \
\
/* Eval */ \
F(EvalReceiver, 1) \
F(GlobalReceiver, 1) \
F(SetInPotentiallyDirectEval, 0) \
F(ClearInPotentiallyDirectEval, 0) \
F(InDirectEval, 0) \
F(ExecDirectEval, 1) \
\
F(SetProperty, -1 /* 3 or 4 */) \
F(IgnoreAttributesAndSetProperty, -1 /* 3 or 4 */) \
......
......@@ -102,6 +102,7 @@ void Top::InitializeThreadLocal() {
clear_scheduled_exception();
thread_local_.save_context_ = NULL;
thread_local_.catcher_ = NULL;
thread_local_.in_potentially_direct_eval_ = false;
}
......
......@@ -67,6 +67,10 @@ class ThreadLocalTop BASE_EMBEDDED {
// Call back function to report unsafe JS accesses.
v8::FailedAccessCheckCallback failed_access_check_callback_;
// Flag for whether we are currently in a call that the static analysis
// marked as a potentially direct eval.
bool in_potentially_direct_eval_;
};
#define TOP_ADDRESS_LIST(C) \
......@@ -165,6 +169,14 @@ class Top {
}
static inline Address* handler_address() { return &thread_local_.handler_; }
static inline void set_in_potentially_direct_eval(bool b) {
thread_local_.in_potentially_direct_eval_ = b;
}
static inline bool is_in_potentially_direct_eval() {
return thread_local_.in_potentially_direct_eval_;
}
// Generated code scratch locations.
static void* formal_count_address() { return &thread_local_.formal_count_; }
......
......@@ -101,7 +101,6 @@ function GlobalParseFloat(string) {
return %StringParseFloat(ToString(string));
}
function GlobalEval(x) {
if (!IS_STRING(x)) return x;
......@@ -110,10 +109,14 @@ function GlobalEval(x) {
'be the global object from which eval originated');
}
var f = %CompileString(x, 0, true);
if (%InDirectEval()) {
return %ExecDirectEval(x);
}
var f = %CompileString(x, 0);
if (!IS_FUNCTION(f)) return f;
return f.call(%EvalReceiver(this));
return f.call(this);
}
......@@ -121,7 +124,7 @@ function GlobalEval(x) {
function GlobalExecScript(expr, lang) {
// NOTE: We don't care about the character casing.
if (!lang || /javascript/i.test(lang)) {
var f = %CompileString(ToString(expr), 0, false);
var f = %CompileString(ToString(expr), 0);
f.call(%GlobalReceiver(global));
}
return null;
......@@ -140,7 +143,7 @@ function SetupGlobal() {
// ECMA-262 - 15.1.1.3.
%SetProperty(global, "undefined", void 0, DONT_ENUM | DONT_DELETE);
// Setup non-enumerable function on the global object.
InstallFunctions(global, DONT_ENUM, $Array(
"isNaN", GlobalIsNaN,
......@@ -521,7 +524,7 @@ function NewFunction(arg1) { // length == 1
// The call to SetNewFunctionAttributes will ensure the prototype
// property of the resulting function is enumerable (ECMA262, 15.3.5.2).
var f = %CompileString(source, -1, false)();
var f = %CompileString(source, -1)();
%FunctionSetName(f, "anonymous");
return %SetNewFunctionAttributes(f);
}
......
......@@ -4022,7 +4022,7 @@ THREADED_TEST(Eval) {
v8::HandleScope scope;
LocalContext current;
// Test that un-aliased eval uses local context.
// Test that un-aliased eval reads from local context.
Local<Script> script =
Script::Compile(v8_str("foo = 0;"
"(function() {"
......@@ -4032,6 +4032,19 @@ THREADED_TEST(Eval) {
Local<Value> result = script->Run();
CHECK_EQ(2, result->Int32Value());
// Test that un-aliased eval writes to local context.
script =
Script::Compile(v8_str("foo = 0;"
"(function() {"
" var foo = 1;"
" eval('foo = 2;');"
" return foo;"
"})();"));
result = script->Run();
CHECK_EQ(0, current->Global()->Get(v8_str("foo"))->Int32Value());
CHECK(result->IsInt32());
CHECK_EQ(2, result->Int32Value());
// Test that un-aliased eval has right this.
script =
Script::Compile(v8_str("function MyObject() { this.self = eval('this'); }"
......@@ -4039,6 +4052,120 @@ THREADED_TEST(Eval) {
"o === o.self"));
result = script->Run();
CHECK(result->IsTrue());
// Test that aliased eval reads from global context.
script =
Script::Compile(v8_str("var e = eval;"
"foo = 0;"
"(function() {"
" var foo = 2;"
" return e('foo');"
"})();"));
result = script->Run();
CHECK_EQ(0, result->Int32Value());
// Test that aliased eval writes to global context.
script =
Script::Compile(v8_str("var e = eval;"
"foo = 0;"
"(function() {"
" e('var foo = 2;');"
"})();"
"foo"));
result = script->Run();
CHECK_EQ(2, result->Int32Value());
// Test that aliased eval has right this.
script =
Script::Compile(v8_str("var e = eval;"
"function MyObject() { this.self = e('this'); }"
"var o = new MyObject();"
"this === o.self"));
result = script->Run();
CHECK(result->IsTrue());
// Try to cheat the 'aliased eval' detection.
script =
Script::Compile(v8_str("var x = this;"
"foo = 0;"
"(function() {"
" var foo = 2;"
" return x.eval('foo');"
"})();"));
result = script->Run();
CHECK_EQ(0, result->Int32Value());
script =
Script::Compile(v8_str("var e = eval;"
"foo = 0;"
"(function() {"
" var eval = function(x) { return x; };"
" var foo = eval(2);"
" return e('foo');"
"})();"));
result = script->Run();
CHECK_EQ(0, result->Int32Value());
script =
Script::Compile(v8_str("(function() {"
" var eval = function(x) { return 2 * x; };"
" return (function() { return eval(2); })();"
"})();"));
result = script->Run();
CHECK_EQ(4, result->Int32Value());
script =
Script::Compile(v8_str("eval = function(x) { return 2 * x; };"
"(function() {"
" return (function() { return eval(2); })();"
"})();"));
result = script->Run();
CHECK_EQ(4, result->Int32Value());
}
THREADED_TEST(EvalAliasedDynamic) {
v8::HandleScope scope;
LocalContext current;
// This sets 'global' to the real global object (as opposed to the
// proxy). It is highly implementation dependent, so take care.
current->Global()->Set(v8_str("global"), current->Global()->GetPrototype());
// Tests where aliased eval can only be resolved dynamically.
Local<Script> script =
Script::Compile(v8_str("function f(x) { "
" var foo = 2;"
" with (x) { return eval('foo'); }"
"}"
"foo = 0;"
"var result = f(new Object()) == 2;"
"result = result && (f(global) == 0);"
"var x = new Object();"
"x.eval = function(x) { return 1; };"
"result = result && (f(x) == 1);"
"result"));
Local<Value> result = script->Run();
CHECK(result->IsTrue());
v8::TryCatch try_catch;
script =
Script::Compile(v8_str("function f(x) { "
" var bar = 2;"
" with (x) { return eval('bar'); }"
"}"
"f(global)"));
result = script->Run();
CHECK(try_catch.HasCaught());
try_catch.Reset();
script =
Script::Compile(v8_str("this.eval = function (x) { return x + x; };"
"foo = 2;"
"eval('foo')"));
result = script->Run();
CHECK(result->IsString());
CHECK_EQ(v8_str("foofoo"), result);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment