зеркало из https://github.com/mozilla/pjs.git
Merge tracemonkey to mozilla-central.
This commit is contained in:
Коммит
57905eb5c0
|
@ -72,7 +72,7 @@ var tokens = [
|
|||
// Nonterminal tree node type codes.
|
||||
"SCRIPT", "BLOCK", "LABEL", "FOR_IN", "CALL", "NEW_WITH_ARGS", "INDEX",
|
||||
"ARRAY_INIT", "OBJECT_INIT", "PROPERTY_INIT", "GETTER", "SETTER",
|
||||
"GROUP", "LIST", "LET_STM", "LET_EXP", "LET_DEF",
|
||||
"GROUP", "LIST", "LET_BLOCK", "ARRAY_COMP", "GENERATOR", "COMP_TAIL",
|
||||
|
||||
// Terminals.
|
||||
"IDENTIFIER", "NUMBER", "STRING", "REGEXP",
|
||||
|
@ -81,7 +81,7 @@ var tokens = [
|
|||
"break",
|
||||
"case", "catch", "const", "continue",
|
||||
"debugger", "default", "delete", "do",
|
||||
"else", "enum",
|
||||
"else",
|
||||
"false", "finally", "for", "function",
|
||||
"if", "in", "instanceof",
|
||||
"let",
|
||||
|
|
|
@ -71,10 +71,27 @@ var global = {
|
|||
x2.scope = x.scope;
|
||||
ExecutionContext.current = x2;
|
||||
try {
|
||||
execute(parse(s), x2);
|
||||
execute(parse(new VanillaBuilder, s), x2);
|
||||
} catch (e if e == THROW) {
|
||||
x.result = x2.result;
|
||||
throw e;
|
||||
} catch (e if e instanceof SyntaxError) {
|
||||
x.result = e;
|
||||
throw THROW;
|
||||
} catch (e if e instanceof InternalError) {
|
||||
/*
|
||||
* If we get too much recursion during parsing we need to re-throw
|
||||
* it as a narcissus THROW.
|
||||
*
|
||||
* See bug 152646.
|
||||
*/
|
||||
var re = /InternalError: (script stack space quota is exhausted|too much recursion)/;
|
||||
if (re.test(e.toString())) {
|
||||
x.result = e;
|
||||
throw THROW;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
ExecutionContext.current = x;
|
||||
}
|
||||
|
@ -106,8 +123,9 @@ var global = {
|
|||
var t = new Tokenizer("anonymous(" + p + ") {" + b + "}");
|
||||
|
||||
// NB: Use the STATEMENT_FORM constant since we don't want to push this
|
||||
// function onto the null compilation context.
|
||||
var f = FunctionDefinition(t, null, false, STATEMENT_FORM);
|
||||
// function onto the fake compilation context.
|
||||
var x = { builder: new VanillaBuilder };
|
||||
var f = FunctionDefinition(t, x, false, STATEMENT_FORM);
|
||||
var s = {object: global, parent: null};
|
||||
return newFunction(f,{scope:s});
|
||||
},
|
||||
|
@ -482,7 +500,7 @@ function execute(n, x) {
|
|||
|
||||
case ASSIGN:
|
||||
r = execute(n[0], x);
|
||||
t = n[0].assignOp;
|
||||
t = n.assignOp;
|
||||
if (t)
|
||||
u = getValue(r);
|
||||
v = getValue(execute(n[1], x));
|
||||
|
@ -1007,7 +1025,7 @@ function evaluate(s, f, l) {
|
|||
var x2 = new ExecutionContext(GLOBAL_CODE);
|
||||
ExecutionContext.current = x2;
|
||||
try {
|
||||
execute(parse(s, f, l), x2);
|
||||
execute(parse(new VanillaBuilder, s, f, l), x2);
|
||||
} catch (e if e == THROW) {
|
||||
if (x) {
|
||||
x.result = x2.result;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* vim: set sw=4 ts=8 et tw=78: */
|
||||
/* vim: set sw=4 ts=4 et tw=78: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
|
@ -57,7 +57,9 @@ for (var op in opTypeNames) {
|
|||
}
|
||||
}
|
||||
|
||||
// file ptr, path to file, line number -> Tokenizer
|
||||
/*
|
||||
* Tokenizer :: (file ptr, path, line number) -> Tokenizer
|
||||
*/
|
||||
function Tokenizer(s, f, l) {
|
||||
this.cursor = 0;
|
||||
this.source = String(s);
|
||||
|
@ -65,22 +67,23 @@ function Tokenizer(s, f, l) {
|
|||
this.tokenIndex = 0;
|
||||
this.lookahead = 0;
|
||||
this.scanNewlines = false;
|
||||
this.scanOperand = true;
|
||||
this.filename = f || "";
|
||||
this.lineno = l || 1;
|
||||
}
|
||||
|
||||
Tokenizer.prototype = {
|
||||
get done() {
|
||||
return this.peek() == END;
|
||||
// We need to set scanOperand to true here because the first thing
|
||||
// might be a regexp.
|
||||
return this.peek(true) == END;
|
||||
},
|
||||
|
||||
get token() {
|
||||
return this.tokens[this.tokenIndex];
|
||||
},
|
||||
|
||||
match: function (tt) {
|
||||
return this.get() == tt || this.unget();
|
||||
match: function (tt, scanOperand) {
|
||||
return this.get(scanOperand) == tt || this.unget();
|
||||
},
|
||||
|
||||
mustMatch: function (tt) {
|
||||
|
@ -89,7 +92,7 @@ Tokenizer.prototype = {
|
|||
return this.token;
|
||||
},
|
||||
|
||||
peek: function () {
|
||||
peek: function (scanOperand) {
|
||||
var tt, next;
|
||||
if (this.lookahead) {
|
||||
next = this.tokens[(this.tokenIndex + this.lookahead) & 3];
|
||||
|
@ -97,15 +100,15 @@ Tokenizer.prototype = {
|
|||
? NEWLINE
|
||||
: next.type;
|
||||
} else {
|
||||
tt = this.get();
|
||||
tt = this.get(scanOperand);
|
||||
this.unget();
|
||||
}
|
||||
return tt;
|
||||
},
|
||||
|
||||
peekOnSameLine: function () {
|
||||
peekOnSameLine: function (scanOperand) {
|
||||
this.scanNewlines = true;
|
||||
var tt = this.peek();
|
||||
var tt = this.peek(scanOperand);
|
||||
this.scanNewlines = false;
|
||||
return tt;
|
||||
},
|
||||
|
@ -334,13 +337,6 @@ Tokenizer.prototype = {
|
|||
op += '=';
|
||||
} else {
|
||||
token.type = tokenIds[opTypeNames[op]];
|
||||
if (this.scanOperand) {
|
||||
switch (token.type) {
|
||||
case PLUS: token.type = UNARY_PLUS; break;
|
||||
case MINUS: token.type = UNARY_MINUS; break;
|
||||
}
|
||||
}
|
||||
|
||||
token.assignOp = null;
|
||||
}
|
||||
|
||||
|
@ -364,10 +360,13 @@ Tokenizer.prototype = {
|
|||
token.value = id;
|
||||
},
|
||||
|
||||
// void -> token type
|
||||
// It consumes input *only* if there is no lookahead.
|
||||
// Dispatch to the appropriate lexing function depending on the input.
|
||||
get: function () {
|
||||
/*
|
||||
* Tokenizer.get :: void -> token type
|
||||
*
|
||||
* Consumes input *only* if there is no lookahead.
|
||||
* Dispatch to the appropriate lexing function depending on the input.
|
||||
*/
|
||||
get: function (scanOperand) {
|
||||
var token;
|
||||
while (this.lookahead) {
|
||||
--this.lookahead;
|
||||
|
@ -395,7 +394,7 @@ Tokenizer.prototype = {
|
|||
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
|
||||
ch === '$' || ch === '_') {
|
||||
this.lexIdent(ch);
|
||||
} else if (this.scanOperand && ch === '/') {
|
||||
} else if (scanOperand && ch === '/') {
|
||||
this.lexRegExp(ch);
|
||||
} else if (ch in opTokens) {
|
||||
this.lexOp(ch);
|
||||
|
@ -419,8 +418,11 @@ Tokenizer.prototype = {
|
|||
return token.type;
|
||||
},
|
||||
|
||||
// void -> undefined
|
||||
// match depends on unget returning undefined.
|
||||
/*
|
||||
* Tokenizer.unget :: void -> undefined
|
||||
*
|
||||
* Match depends on unget returning undefined.
|
||||
*/
|
||||
unget: function () {
|
||||
if (++this.lookahead == 4) throw "PANIC: too much lookahead!";
|
||||
this.tokenIndex = (this.tokenIndex - 1) & 3;
|
||||
|
@ -431,6 +433,25 @@ Tokenizer.prototype = {
|
|||
e.source = this.source;
|
||||
e.cursor = this.cursor;
|
||||
return e;
|
||||
},
|
||||
|
||||
save: function () {
|
||||
return {
|
||||
cursor: this.cursor,
|
||||
tokenIndex: this.tokenIndex,
|
||||
tokens: this.tokens.slice(),
|
||||
lookahead: this.lookahead,
|
||||
scanNewlines: this.scanNewlines,
|
||||
lineno: this.lineno
|
||||
};
|
||||
},
|
||||
|
||||
rewind: function(point) {
|
||||
this.cursor = point.cursor;
|
||||
this.tokenIndex = point.tokenIndex;
|
||||
this.tokens = point.tokens.slice();
|
||||
this.lookahead = point.lookahead;
|
||||
this.scanNewline = point.scanNewline;
|
||||
this.lineno = point.lineno;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -546,7 +546,7 @@ static JSBool js_NewRuntimeWasCalled = JS_FALSE;
|
|||
#endif
|
||||
|
||||
JSRuntime::JSRuntime()
|
||||
: gcChunkAllocator(&defaultGCChunkAllocator)
|
||||
: gcChunkAllocator(&defaultGCChunkAllocator)
|
||||
{
|
||||
/* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
|
||||
JS_INIT_CLIST(&contextList);
|
||||
|
@ -557,6 +557,16 @@ JSRuntime::JSRuntime()
|
|||
bool
|
||||
JSRuntime::init(uint32 maxbytes)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
functionMeterFilename = getenv("JS_FUNCTION_STATFILE");
|
||||
if (functionMeterFilename) {
|
||||
if (!methodReadBarrierCountMap.init())
|
||||
return false;
|
||||
if (!unjoinedFunctionCountMap.init())
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!(defaultCompartment = new JSCompartment(this)) ||
|
||||
!defaultCompartment->init()) {
|
||||
return false;
|
||||
|
|
|
@ -1778,8 +1778,8 @@ sort_compare_strings(void *arg, const void *a, const void *b, int *result)
|
|||
return JS_TRUE;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
array_sort(JSContext *cx, uintN argc, Value *vp)
|
||||
JSBool
|
||||
js::array_sort(JSContext *cx, uintN argc, Value *vp)
|
||||
{
|
||||
jsuint len, newlen, i, undefs;
|
||||
size_t elemsize;
|
||||
|
|
|
@ -190,6 +190,15 @@ extern bool
|
|||
js_MergeSort(void *vec, size_t nel, size_t elsize, JSComparator cmp,
|
||||
void *arg, void *tmp, JSMergeSortElemType elemType);
|
||||
|
||||
/*
|
||||
* The Array.prototype.sort fast-native entry point is exported for joined
|
||||
* function optimization in js{interp,tracer}.cpp.
|
||||
*/
|
||||
namespace js {
|
||||
extern JSBool
|
||||
array_sort(JSContext *cx, uintN argc, js::Value *vp);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ARRAYS
|
||||
extern JSBool
|
||||
js_ArrayInfo(JSContext *cx, JSObject *obj, uintN argc, js::Value *argv, js::Value *rval);
|
||||
|
|
|
@ -959,76 +959,87 @@ private:
|
|||
FILE *mFile;
|
||||
};
|
||||
|
||||
#ifdef JS_EVAL_CACHE_METERING
|
||||
static void
|
||||
DumpEvalCacheMeter(JSContext *cx)
|
||||
{
|
||||
struct {
|
||||
const char *name;
|
||||
ptrdiff_t offset;
|
||||
} table[] = {
|
||||
if (const char *filename = getenv("JS_EVALCACHE_STATFILE")) {
|
||||
struct {
|
||||
const char *name;
|
||||
ptrdiff_t offset;
|
||||
} table[] = {
|
||||
#define frob(x) { #x, offsetof(JSEvalCacheMeter, x) }
|
||||
EVAL_CACHE_METER_LIST(frob)
|
||||
EVAL_CACHE_METER_LIST(frob)
|
||||
#undef frob
|
||||
};
|
||||
JSEvalCacheMeter *ecm = &JS_THREAD_DATA(cx)->evalCacheMeter;
|
||||
};
|
||||
JSEvalCacheMeter *ecm = &JS_THREAD_DATA(cx)->evalCacheMeter;
|
||||
|
||||
static JSAutoFile fp;
|
||||
if (!fp) {
|
||||
fp.open("/tmp/evalcache.stats", "w");
|
||||
if (!fp)
|
||||
static JSAutoFile fp;
|
||||
if (!fp && !fp.open(filename, "w"))
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(fp, "eval cache meter (%p):\n",
|
||||
fprintf(fp, "eval cache meter (%p):\n",
|
||||
#ifdef JS_THREADSAFE
|
||||
(void *) cx->thread
|
||||
(void *) cx->thread
|
||||
#else
|
||||
(void *) cx->runtime
|
||||
(void *) cx->runtime
|
||||
#endif
|
||||
);
|
||||
for (uintN i = 0; i < JS_ARRAY_LENGTH(table); ++i) {
|
||||
fprintf(fp, "%-8.8s %llu\n",
|
||||
table[i].name,
|
||||
(unsigned long long int) *(uint64 *)((uint8 *)ecm + table[i].offset));
|
||||
);
|
||||
for (uintN i = 0; i < JS_ARRAY_LENGTH(table); ++i) {
|
||||
fprintf(fp, "%-8.8s %llu\n",
|
||||
table[i].name,
|
||||
(unsigned long long int) *(uint64 *)((uint8 *)ecm + table[i].offset));
|
||||
}
|
||||
fprintf(fp, "hit ratio %g%%\n", ecm->hit * 100. / ecm->probe);
|
||||
fprintf(fp, "avg steps %g\n", double(ecm->step) / ecm->probe);
|
||||
fflush(fp);
|
||||
}
|
||||
fprintf(fp, "hit ratio %g%%\n", ecm->hit * 100. / ecm->probe);
|
||||
fprintf(fp, "avg steps %g\n", double(ecm->step) / ecm->probe);
|
||||
fflush(fp);
|
||||
}
|
||||
# define DUMP_EVAL_CACHE_METER(cx) DumpEvalCacheMeter(cx)
|
||||
#endif
|
||||
|
||||
#ifdef JS_FUNCTION_METERING
|
||||
static void
|
||||
DumpFunctionCountMap(const char *title, JSRuntime::FunctionCountMap &map, FILE *fp)
|
||||
{
|
||||
fprintf(fp, "\n%s count map:\n", title);
|
||||
|
||||
for (JSRuntime::FunctionCountMap::Range r = map.all(); !r.empty(); r.popFront()) {
|
||||
JSFunction *fun = r.front().key;
|
||||
int32 count = r.front().value;
|
||||
|
||||
fprintf(fp, "%10d %s:%u\n", count, fun->u.i.script->filename, fun->u.i.script->lineno);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
DumpFunctionMeter(JSContext *cx)
|
||||
{
|
||||
struct {
|
||||
const char *name;
|
||||
ptrdiff_t offset;
|
||||
} table[] = {
|
||||
if (const char *filename = cx->runtime->functionMeterFilename) {
|
||||
struct {
|
||||
const char *name;
|
||||
ptrdiff_t offset;
|
||||
} table[] = {
|
||||
#define frob(x) { #x, offsetof(JSFunctionMeter, x) }
|
||||
FUNCTION_KIND_METER_LIST(frob)
|
||||
FUNCTION_KIND_METER_LIST(frob)
|
||||
#undef frob
|
||||
};
|
||||
JSFunctionMeter *fm = &cx->runtime->functionMeter;
|
||||
};
|
||||
JSFunctionMeter *fm = &cx->runtime->functionMeter;
|
||||
|
||||
static JSAutoFile fp;
|
||||
if (!fp) {
|
||||
fp.open("/tmp/function.stats", "a");
|
||||
if (!fp)
|
||||
static JSAutoFile fp;
|
||||
if (!fp && !fp.open(filename, "w"))
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(fp, "function meter (%s):\n", cx->runtime->lastScriptFilename);
|
||||
for (uintN i = 0; i < JS_ARRAY_LENGTH(table); ++i) {
|
||||
fprintf(fp, "%-11.11s %d\n",
|
||||
table[i].name, *(int32 *)((uint8 *)fm + table[i].offset));
|
||||
fprintf(fp, "function meter (%s):\n", cx->runtime->lastScriptFilename);
|
||||
for (uintN i = 0; i < JS_ARRAY_LENGTH(table); ++i)
|
||||
fprintf(fp, "%-19.19s %d\n", table[i].name, *(int32 *)((uint8 *)fm + table[i].offset));
|
||||
|
||||
DumpFunctionCountMap("method read barrier", cx->runtime->methodReadBarrierCountMap, fp);
|
||||
DumpFunctionCountMap("unjoined function", cx->runtime->unjoinedFunctionCountMap, fp);
|
||||
|
||||
putc('\n', fp);
|
||||
fflush(fp);
|
||||
}
|
||||
fflush(fp);
|
||||
}
|
||||
|
||||
# define DUMP_FUNCTION_METER(cx) DumpFunctionMeter(cx)
|
||||
#endif
|
||||
|
||||
#endif /* DEBUG && XP_UNIX */
|
||||
|
||||
|
|
|
@ -919,18 +919,13 @@ struct TraceMonitor {
|
|||
# define JS_ON_TRACE(cx) JS_FALSE
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_brendan
|
||||
# define JS_EVAL_CACHE_METERING 1
|
||||
# define JS_FUNCTION_METERING 1
|
||||
#endif
|
||||
|
||||
/* Number of potentially reusable scriptsToGC to search for the eval cache. */
|
||||
#ifndef JS_EVAL_CACHE_SHIFT
|
||||
# define JS_EVAL_CACHE_SHIFT 6
|
||||
#endif
|
||||
#define JS_EVAL_CACHE_SIZE JS_BIT(JS_EVAL_CACHE_SHIFT)
|
||||
|
||||
#ifdef JS_EVAL_CACHE_METERING
|
||||
#ifdef DEBUG
|
||||
# define EVAL_CACHE_METER_LIST(_) _(probe), _(hit), _(step), _(noscope)
|
||||
# define identity(x) x
|
||||
|
||||
|
@ -941,10 +936,14 @@ struct JSEvalCacheMeter {
|
|||
# undef identity
|
||||
#endif
|
||||
|
||||
#ifdef JS_FUNCTION_METERING
|
||||
#ifdef DEBUG
|
||||
# define FUNCTION_KIND_METER_LIST(_) \
|
||||
_(allfun), _(heavy), _(nofreeupvar), _(onlyfreevar), \
|
||||
_(display), _(flat), _(setupvar), _(badfunarg)
|
||||
_(display), _(flat), _(setupvar), _(badfunarg), \
|
||||
_(joinedsetmethod), _(joinedinitmethod), \
|
||||
_(joinedreplace), _(joinedsort), _(joinedmodulepat), \
|
||||
_(mreadbarrier), _(mwritebarrier), _(mwslotbarrier), \
|
||||
_(unjoined)
|
||||
# define identity(x) x
|
||||
|
||||
struct JSFunctionMeter {
|
||||
|
@ -952,8 +951,13 @@ struct JSFunctionMeter {
|
|||
};
|
||||
|
||||
# undef identity
|
||||
|
||||
# define JS_FUNCTION_METER(cx,x) JS_RUNTIME_METER((cx)->runtime, functionMeter.x)
|
||||
#else
|
||||
# define JS_FUNCTION_METER(cx,x) ((void)0)
|
||||
#endif
|
||||
|
||||
|
||||
#define NATIVE_ITER_CACHE_LOG2 8
|
||||
#define NATIVE_ITER_CACHE_MASK JS_BITMASK(NATIVE_ITER_CACHE_LOG2)
|
||||
#define NATIVE_ITER_CACHE_SIZE JS_BIT(NATIVE_ITER_CACHE_LOG2)
|
||||
|
@ -999,7 +1003,7 @@ struct JSThreadData {
|
|||
/* Lock-free hashed lists of scripts created by eval to garbage-collect. */
|
||||
JSScript *scriptsToGC[JS_EVAL_CACHE_SIZE];
|
||||
|
||||
#ifdef JS_EVAL_CACHE_METERING
|
||||
#ifdef DEBUG
|
||||
JSEvalCacheMeter evalCacheMeter;
|
||||
#endif
|
||||
|
||||
|
@ -1603,9 +1607,22 @@ struct JSRuntime {
|
|||
JSGCStats gcStats;
|
||||
#endif
|
||||
|
||||
#ifdef JS_FUNCTION_METERING
|
||||
#ifdef DEBUG
|
||||
/*
|
||||
* If functionMeterFilename, set from an envariable in JSRuntime's ctor, is
|
||||
* null, the remaining members in this ifdef'ed group are not initialized.
|
||||
*/
|
||||
const char *functionMeterFilename;
|
||||
JSFunctionMeter functionMeter;
|
||||
char lastScriptFilename[1024];
|
||||
|
||||
typedef js::HashMap<JSFunction *,
|
||||
int32,
|
||||
js::DefaultHasher<JSFunction *>,
|
||||
js::SystemAllocPolicy> FunctionCountMap;
|
||||
|
||||
FunctionCountMap methodReadBarrierCountMap;
|
||||
FunctionCountMap unjoinedFunctionCountMap;
|
||||
#endif
|
||||
|
||||
JSWrapObjectCallback wrapObjectCallback;
|
||||
|
@ -1646,7 +1663,7 @@ struct JSRuntime {
|
|||
#define JS_TRACE_MONITOR(cx) (JS_THREAD_DATA(cx)->traceMonitor)
|
||||
#define JS_SCRIPTS_TO_GC(cx) (JS_THREAD_DATA(cx)->scriptsToGC)
|
||||
|
||||
#ifdef JS_EVAL_CACHE_METERING
|
||||
#ifdef DEBUG
|
||||
# define EVAL_CACHE_METER(x) (JS_THREAD_DATA(cx)->evalCacheMeter.x++)
|
||||
#else
|
||||
# define EVAL_CACHE_METER(x) ((void) 0)
|
||||
|
@ -2174,7 +2191,7 @@ JS_ALWAYS_INLINE jsbytecode *
|
|||
JSStackFrame::pc(JSContext *cx) const
|
||||
{
|
||||
JS_ASSERT(cx->containingSegment(this) != NULL);
|
||||
return cx->fp == this ? cx->regs->pc : savedPC;
|
||||
return (cx->fp == this) ? cx->regs->pc : savedPC;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -1270,6 +1270,17 @@ JS_GetFrameCalleeObject(JSContext *cx, JSStackFrame *fp)
|
|||
return fp->callee();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_GetValidFrameCalleeObject(JSContext *cx, JSStackFrame *fp, jsval *vp)
|
||||
{
|
||||
Value v;
|
||||
|
||||
if (!fp->getValidCalleeObject(cx, &v))
|
||||
return false;
|
||||
*vp = Jsvalify(v);
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_IsDebuggerFrame(JSContext *cx, JSStackFrame *fp)
|
||||
{
|
||||
|
|
|
@ -272,11 +272,42 @@ extern JS_PUBLIC_API(void)
|
|||
JS_SetFrameReturnValue(JSContext *cx, JSStackFrame *fp, jsval rval);
|
||||
|
||||
/**
|
||||
* Return fp's callee function object (fp->callee) if it has one.
|
||||
* Return fp's callee function object (fp->callee) if it has one. Note that
|
||||
* this API cannot fail. A null return means "no callee": fp is a global or
|
||||
* eval-from-global frame, not a call frame.
|
||||
*
|
||||
* This API began life as an infallible getter, but now it can return either:
|
||||
*
|
||||
* 1. An optimized closure that was compiled assuming the function could not
|
||||
* escape and be called from sites the compiler could not see.
|
||||
*
|
||||
* 2. A "joined function object", an optimization whereby SpiderMonkey avoids
|
||||
* creating fresh function objects for every evaluation of a function
|
||||
* expression that is used only once by a consumer that either promises to
|
||||
* clone later when asked for the value or that cannot leak the value.
|
||||
*
|
||||
* Because Mozilla's Gecko embedding of SpiderMonkey (and no doubt other
|
||||
* embeddings) calls this API in potentially performance-sensitive ways (e.g.
|
||||
* in nsContentUtils::GetDocumentFromCaller), we are leaving this API alone. It
|
||||
* may now return an unwrapped non-escaping optimized closure, or a joined
|
||||
* function object. Such optimized objects may work well if called from the
|
||||
* correct context, never mutated or compared for identity, etc.
|
||||
*
|
||||
* However, if you really need to get the same callee object that JS code would
|
||||
* see, which means undoing the optimizations, where an undo attempt can fail,
|
||||
* then use JS_GetValidFrameCalleeObject.
|
||||
*/
|
||||
extern JS_PUBLIC_API(JSObject *)
|
||||
JS_GetFrameCalleeObject(JSContext *cx, JSStackFrame *fp);
|
||||
|
||||
/**
|
||||
* Return fp's callee function object after running the deferred closure
|
||||
* cloning "method read barrier". This API can fail! If the frame has no
|
||||
* callee, this API returns true with JSVAL_IS_VOID(*vp).
|
||||
*/
|
||||
extern JS_PUBLIC_API(JSBool)
|
||||
JS_GetValidFrameCalleeObject(JSContext *cx, JSStackFrame *fp, jsval *vp);
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
extern JS_PUBLIC_API(const char *)
|
||||
|
|
|
@ -6633,8 +6633,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
|
|||
bool lambda = PN_OP(init) == JSOP_LAMBDA;
|
||||
if (lambda)
|
||||
++methodInits;
|
||||
if (op == JSOP_INITPROP && lambda && init->pn_funbox->joinable())
|
||||
{
|
||||
if (op == JSOP_INITPROP && lambda && init->pn_funbox->joinable()) {
|
||||
op = JSOP_INITMETHOD;
|
||||
pn2->pn_op = uint8(op);
|
||||
} else {
|
||||
|
|
140
js/src/jsfun.cpp
140
js/src/jsfun.cpp
|
@ -1290,6 +1290,102 @@ JS_PUBLIC_DATA(Class) js_CallClass = {
|
|||
JS_CLASS_TRACE(args_or_call_trace)
|
||||
};
|
||||
|
||||
bool
|
||||
JSStackFrame::getValidCalleeObject(JSContext *cx, Value *vp)
|
||||
{
|
||||
if (!fun) {
|
||||
*vp = argv ? argv[-2] : UndefinedValue();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* See the equivalent condition in args_getProperty for ARGS_CALLEE, but
|
||||
* note that here we do not want to throw, since this escape can happen via
|
||||
* a foo.caller reference alone, without any debugger or indirect eval. And
|
||||
* alas, it seems foo.caller is still used on the Web.
|
||||
*/
|
||||
if (fun->needsWrapper()) {
|
||||
JSObject *wrapper = WrapEscapingClosure(cx, this, fun, fun);
|
||||
if (!wrapper)
|
||||
return false;
|
||||
vp->setObject(*wrapper);
|
||||
return true;
|
||||
}
|
||||
|
||||
JSObject *funobj = &calleeObject();
|
||||
vp->setObject(*funobj);
|
||||
|
||||
/*
|
||||
* Check for an escape attempt by a joined function object, which must go
|
||||
* through the frame's |this| object's method read barrier for the method
|
||||
* atom by which it was uniquely associated with a property.
|
||||
*/
|
||||
if (thisv.isObject()) {
|
||||
JS_ASSERT(GET_FUNCTION_PRIVATE(cx, funobj) == fun);
|
||||
|
||||
if (fun == funobj && fun->methodAtom()) {
|
||||
JSObject *thisp = &thisv.toObject();
|
||||
JS_ASSERT(thisp->canHaveMethodBarrier());
|
||||
|
||||
JSScope *scope = thisp->scope();
|
||||
if (scope->hasMethodBarrier()) {
|
||||
JSScopeProperty *sprop = scope->lookup(ATOM_TO_JSID(fun->methodAtom()));
|
||||
|
||||
/*
|
||||
* The method property might have been deleted while the method
|
||||
* barrier scope flag stuck, so we must lookup and test here.
|
||||
*
|
||||
* Two cases follow: the method barrier was not crossed yet, so
|
||||
* we cross it here; the method barrier *was* crossed, in which
|
||||
* case we must fetch and validate the cloned (unjoined) funobj
|
||||
* in the method property's slot.
|
||||
*
|
||||
* In either case we must allow for the method property to have
|
||||
* been replaced, or its value to have been overwritten.
|
||||
*/
|
||||
if (sprop) {
|
||||
if (sprop->isMethod() && &sprop->methodObject() == funobj) {
|
||||
if (!scope->methodReadBarrier(cx, sprop, vp))
|
||||
return false;
|
||||
setCalleeObject(vp->toObject());
|
||||
return true;
|
||||
}
|
||||
if (sprop->hasSlot()) {
|
||||
Value v = thisp->getSlot(sprop->slot);
|
||||
JSObject *clone;
|
||||
|
||||
if (IsFunctionObject(v, &clone) &&
|
||||
GET_FUNCTION_PRIVATE(cx, clone) == fun &&
|
||||
clone->hasMethodObj(*thisp)) {
|
||||
JS_ASSERT(clone != funobj);
|
||||
*vp = v;
|
||||
setCalleeObject(*clone);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If control flows here, we can't find an already-existing
|
||||
* clone (or force to exist a fresh clone) created via thisp's
|
||||
* method read barrier, so we must clone fun and store it in
|
||||
* fp's callee to avoid re-cloning upon repeated foo.caller
|
||||
* access. It seems that there are no longer any properties
|
||||
* referring to fun.
|
||||
*/
|
||||
funobj = CloneFunctionObject(cx, fun, fun->getParent());
|
||||
if (!funobj)
|
||||
return false;
|
||||
funobj->setMethodObj(*thisp);
|
||||
setCalleeObject(*funobj);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Generic function tinyids. */
|
||||
enum {
|
||||
FUN_ARGUMENTS = -1, /* predefined arguments local variable */
|
||||
|
@ -1303,7 +1399,7 @@ static JSBool
|
|||
fun_getProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
|
||||
{
|
||||
if (!JSID_IS_INT(id))
|
||||
return JS_TRUE;
|
||||
return true;
|
||||
|
||||
jsint slot = JSID_TO_INT(id);
|
||||
|
||||
|
@ -1331,10 +1427,10 @@ fun_getProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
|
|||
while (!(fun = (JSFunction *)
|
||||
GetInstancePrivate(cx, obj, &js_FunctionClass, NULL))) {
|
||||
if (slot != FUN_LENGTH)
|
||||
return JS_TRUE;
|
||||
return true;
|
||||
obj = obj->getProto();
|
||||
if (!obj)
|
||||
return JS_TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Find fun's top-most activation record. */
|
||||
|
@ -1353,11 +1449,11 @@ fun_getProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
|
|||
js_GetErrorMessage, NULL,
|
||||
JSMSG_DEPRECATED_USAGE,
|
||||
js_arguments_str)) {
|
||||
return JS_FALSE;
|
||||
return false;
|
||||
}
|
||||
if (fp) {
|
||||
if (!js_GetArgsValue(cx, fp, vp))
|
||||
return JS_FALSE;
|
||||
return false;
|
||||
} else {
|
||||
vp->setNull();
|
||||
}
|
||||
|
@ -1374,30 +1470,12 @@ fun_getProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
|
|||
break;
|
||||
|
||||
case FUN_CALLER:
|
||||
if (fp && fp->down && fp->down->fun) {
|
||||
JSFunction *caller = fp->down->fun;
|
||||
/*
|
||||
* See equivalent condition in args_getProperty for ARGS_CALLEE,
|
||||
* but here we do not want to throw, since this escape can happen
|
||||
* via foo.caller alone, without any debugger or indirect eval. And
|
||||
* it seems foo.caller is still used on the Web.
|
||||
*/
|
||||
if (caller->needsWrapper()) {
|
||||
JSObject *wrapper = WrapEscapingClosure(cx, fp->down, FUN_OBJECT(caller), caller);
|
||||
if (!wrapper)
|
||||
return JS_FALSE;
|
||||
vp->setObject(*wrapper);
|
||||
return JS_TRUE;
|
||||
}
|
||||
vp->setNull();
|
||||
if (fp && fp->down && !fp->down->getValidCalleeObject(cx, vp))
|
||||
return false;
|
||||
|
||||
JS_ASSERT(fp->down->argv);
|
||||
*vp = fp->down->calleeValue();
|
||||
} else {
|
||||
vp->setNull();
|
||||
}
|
||||
|
||||
/* Censor the caller if it is from another compartment. */
|
||||
if (vp->isObject()) {
|
||||
/* Censor the caller if it is from another compartment. */
|
||||
if (vp->toObject().getCompartment(cx) != cx->compartment)
|
||||
vp->setNull();
|
||||
}
|
||||
|
@ -1410,7 +1488,7 @@ fun_getProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
|
|||
break;
|
||||
}
|
||||
|
||||
return JS_TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct LazyFunctionProp {
|
||||
|
@ -1842,7 +1920,8 @@ JSFunction::countInterpretedReservedSlots() const
|
|||
*/
|
||||
JS_PUBLIC_DATA(Class) js_FunctionClass = {
|
||||
js_Function_str,
|
||||
JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(2) |
|
||||
JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE |
|
||||
JSCLASS_HAS_RESERVED_SLOTS(JSObject::FUN_FIXED_RESERVED_SLOTS) |
|
||||
JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_Function),
|
||||
PropertyStub, /* addProperty */
|
||||
PropertyStub, /* delProperty */
|
||||
|
@ -2182,8 +2261,7 @@ Function(JSContext *cx, JSObject *obj, uintN argc, Value *argv, Value *rval)
|
|||
* js_CheckContentSecurityPolicy is defined in jsobj.cpp
|
||||
*/
|
||||
if (!js_CheckContentSecurityPolicy(cx)) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
||||
JSMSG_CSP_BLOCKED_FUNCTION);
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CSP_BLOCKED_FUNCTION);
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@
|
|||
#include "jsprvtd.h"
|
||||
#include "jspubtd.h"
|
||||
#include "jsobj.h"
|
||||
#include "jsatom.h"
|
||||
#include "jsstr.h"
|
||||
|
||||
typedef struct JSLocalNameMap JSLocalNameMap;
|
||||
|
||||
|
@ -95,6 +97,10 @@ typedef union JSLocalNames {
|
|||
* can move to u.i.script->flags. For now we use function flag bits to minimize
|
||||
* pointer-chasing.
|
||||
*/
|
||||
#define JSFUN_JOINABLE 0x0001 /* function is null closure that does not
|
||||
appear to call itself via its own name
|
||||
or arguments.callee */
|
||||
|
||||
#define JSFUN_EXPR_CLOSURE 0x1000 /* expression closure: function(x) x*x */
|
||||
#define JSFUN_TRCINFO 0x2000 /* when set, u.n.trcinfo is non-null,
|
||||
JSFunctionSpec::call points to a
|
||||
|
@ -202,6 +208,44 @@ struct JSFunction : public JSObject
|
|||
bool mightEscape() const {
|
||||
return FUN_INTERPRETED(this) && (FUN_FLAT_CLOSURE(this) || u.i.nupvars == 0);
|
||||
}
|
||||
|
||||
bool joinable() const {
|
||||
return flags & JSFUN_JOINABLE;
|
||||
}
|
||||
|
||||
private:
|
||||
/*
|
||||
* js_FunctionClass reserves two slots, which are free in JSObject::fslots
|
||||
* without requiring dslots allocation. Null closures that can be joined to
|
||||
* a compiler-created function object use the first one to hold a mutable
|
||||
* methodAtom() state variable, needed for correct foo.caller handling.
|
||||
*/
|
||||
enum {
|
||||
METHOD_ATOM_SLOT = JSSLOT_FUN_METHOD_ATOM
|
||||
};
|
||||
|
||||
public:
|
||||
void setJoinable() {
|
||||
JS_ASSERT(FUN_INTERPRETED(this));
|
||||
fslots[METHOD_ATOM_SLOT].setNull();
|
||||
flags |= JSFUN_JOINABLE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Method name imputed from property uniquely assigned to or initialized,
|
||||
* where the function does not need to be cloned to carry a scope chain or
|
||||
* flattened upvars.
|
||||
*/
|
||||
JSAtom *methodAtom() const {
|
||||
return (joinable() && fslots[METHOD_ATOM_SLOT].isString())
|
||||
? STRING_TO_ATOM(fslots[METHOD_ATOM_SLOT].toString())
|
||||
: NULL;
|
||||
}
|
||||
|
||||
void setMethodAtom(JSAtom *atom) {
|
||||
JS_ASSERT(joinable());
|
||||
fslots[METHOD_ATOM_SLOT].setString(ATOM_TO_STRING(atom));
|
||||
}
|
||||
};
|
||||
|
||||
JS_STATIC_ASSERT(sizeof(JSFunction) % JS_GCTHING_ALIGN == 0);
|
||||
|
|
|
@ -2564,6 +2564,19 @@ js_TraceRuntime(JSTracer *trc)
|
|||
*/
|
||||
if (rt->gcExtraRootsTraceOp)
|
||||
rt->gcExtraRootsTraceOp(trc, rt->gcExtraRootsData);
|
||||
|
||||
#ifdef DEBUG
|
||||
if (rt->functionMeterFilename) {
|
||||
for (int k = 0; k < 2; k++) {
|
||||
typedef JSRuntime::FunctionCountMap HM;
|
||||
HM &h = (k == 0) ? rt->methodReadBarrierCountMap : rt->unjoinedFunctionCountMap;
|
||||
for (HM::Range r = h.all(); !r.empty(); r.popFront()) {
|
||||
JSFunction *fun = r.front().key;
|
||||
JS_CALL_OBJECT_TRACER(trc, fun, "FunctionCountMap key");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -2684,15 +2684,15 @@ BEGIN_CASE(JSOP_STOP)
|
|||
== JSOP_CALL_LENGTH);
|
||||
TRACE_0(LeaveFrame);
|
||||
if (!TRACE_RECORDER(cx) && recursive) {
|
||||
if (*(regs.pc + JSOP_CALL_LENGTH) == JSOP_TRACE) {
|
||||
if (regs.pc[JSOP_CALL_LENGTH] == JSOP_TRACE) {
|
||||
regs.pc += JSOP_CALL_LENGTH;
|
||||
MONITOR_BRANCH(Record_LeaveFrame);
|
||||
op = (JSOp)*regs.pc;
|
||||
DO_OP();
|
||||
}
|
||||
}
|
||||
if (*(regs.pc + JSOP_CALL_LENGTH) == JSOP_TRACE ||
|
||||
*(regs.pc + JSOP_CALL_LENGTH) == JSOP_NOP) {
|
||||
if (regs.pc[JSOP_CALL_LENGTH] == JSOP_TRACE ||
|
||||
regs.pc[JSOP_CALL_LENGTH] == JSOP_NOP) {
|
||||
JS_STATIC_ASSERT(JSOP_TRACE_LENGTH == JSOP_NOP_LENGTH);
|
||||
regs.pc += JSOP_CALL_LENGTH;
|
||||
len = JSOP_TRACE_LENGTH;
|
||||
|
@ -4050,7 +4050,8 @@ BEGIN_CASE(JSOP_GETXPROP)
|
|||
jsid id = ATOM_TO_JSID(atom);
|
||||
if (JS_LIKELY(!aobj->getOps()->getProperty)
|
||||
? !js_GetPropertyHelper(cx, obj, id,
|
||||
fp->imacpc
|
||||
(fp->imacpc ||
|
||||
regs.pc[JSOP_GETPROP_LENGTH + i] == JSOP_IFEQ)
|
||||
? JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER
|
||||
: JSGET_CACHE_RESULT | JSGET_METHOD_BARRIER,
|
||||
&rval)
|
||||
|
@ -5672,24 +5673,19 @@ BEGIN_CASE(JSOP_LAMBDA)
|
|||
parent = fp->scopeChain;
|
||||
|
||||
if (obj->getParent() == parent) {
|
||||
op = JSOp(regs.pc[JSOP_LAMBDA_LENGTH]);
|
||||
jsbytecode *pc2 = regs.pc + JSOP_LAMBDA_LENGTH;
|
||||
JSOp op2 = JSOp(*pc2);
|
||||
|
||||
/*
|
||||
* Optimize ({method: function () { ... }, ...}) and
|
||||
* this.method = function () { ... }; bytecode sequences.
|
||||
* Optimize var obj = {method: function () { ... }, ...},
|
||||
* this.method = function () { ... }; and other significant
|
||||
* single-use-of-null-closure bytecode sequences.
|
||||
*
|
||||
* WARNING: code in TraceRecorder::record_JSOP_LAMBDA must
|
||||
* match the optimization cases in the following code that
|
||||
* break from the outer do-while(0).
|
||||
*/
|
||||
if (op == JSOP_SETMETHOD) {
|
||||
#ifdef DEBUG
|
||||
JSOp op2 = JSOp(regs.pc[JSOP_LAMBDA_LENGTH + JSOP_SETMETHOD_LENGTH]);
|
||||
JS_ASSERT(op2 == JSOP_POP || op2 == JSOP_POPV);
|
||||
#endif
|
||||
|
||||
const Value &lref = regs.sp[-1];
|
||||
if (lref.isObject() &&
|
||||
lref.toObject().getClass() == &js_ObjectClass) {
|
||||
break;
|
||||
}
|
||||
} else if (op == JSOP_INITMETHOD) {
|
||||
if (op2 == JSOP_INITMETHOD) {
|
||||
#ifdef DEBUG
|
||||
const Value &lref = regs.sp[-1];
|
||||
JS_ASSERT(lref.isObject());
|
||||
|
@ -5697,9 +5693,86 @@ BEGIN_CASE(JSOP_LAMBDA)
|
|||
JS_ASSERT(obj2->getClass() == &js_ObjectClass);
|
||||
JS_ASSERT(obj2->scope()->object == obj2);
|
||||
#endif
|
||||
|
||||
fun->setMethodAtom(script->getAtom(GET_FULL_INDEX(JSOP_LAMBDA_LENGTH)));
|
||||
JS_FUNCTION_METER(cx, joinedinitmethod);
|
||||
break;
|
||||
}
|
||||
|
||||
if (op2 == JSOP_SETMETHOD) {
|
||||
#ifdef DEBUG
|
||||
op2 = JSOp(pc2[JSOP_SETMETHOD_LENGTH]);
|
||||
JS_ASSERT(op2 == JSOP_POP || op2 == JSOP_POPV);
|
||||
#endif
|
||||
|
||||
const Value &lref = regs.sp[-1];
|
||||
if (lref.isObject() && lref.toObject().canHaveMethodBarrier()) {
|
||||
fun->setMethodAtom(script->getAtom(GET_FULL_INDEX(JSOP_LAMBDA_LENGTH)));
|
||||
JS_FUNCTION_METER(cx, joinedsetmethod);
|
||||
break;
|
||||
}
|
||||
} else if (fun->joinable()) {
|
||||
if (op2 == JSOP_CALL) {
|
||||
/*
|
||||
* Array.prototype.sort and String.prototype.replace are
|
||||
* optimized as if they are special form. We know that they
|
||||
* won't leak the joined function object in obj, therefore
|
||||
* we don't need to clone that compiler- created function
|
||||
* object for identity/mutation reasons.
|
||||
*/
|
||||
int iargc = GET_ARGC(pc2);
|
||||
|
||||
/*
|
||||
* Note that we have not yet pushed obj as the final argument,
|
||||
* so regs.sp[1 - (iargc + 2)], and not regs.sp[-(iargc + 2)],
|
||||
* is the callee for this JSOP_CALL.
|
||||
*/
|
||||
const Value &cref = regs.sp[1 - (iargc + 2)];
|
||||
JSObject *callee;
|
||||
|
||||
if (IsFunctionObject(cref, &callee)) {
|
||||
JSFunction *calleeFun = GET_FUNCTION_PRIVATE(cx, callee);
|
||||
FastNative fastNative = FUN_FAST_NATIVE(calleeFun);
|
||||
|
||||
if (fastNative) {
|
||||
if (iargc == 1 && fastNative == array_sort) {
|
||||
JS_FUNCTION_METER(cx, joinedsort);
|
||||
break;
|
||||
}
|
||||
if (iargc == 2 && fastNative == str_replace) {
|
||||
JS_FUNCTION_METER(cx, joinedreplace);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (op2 == JSOP_NULL) {
|
||||
pc2 += JSOP_NULL_LENGTH;
|
||||
op2 = JSOp(*pc2);
|
||||
|
||||
if (op2 == JSOP_CALL && GET_ARGC(pc2) == 0) {
|
||||
JS_FUNCTION_METER(cx, joinedmodulepat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if (rt->functionMeterFilename) {
|
||||
// No locking, this is mainly for js shell testing.
|
||||
++rt->functionMeter.unjoined;
|
||||
|
||||
typedef JSRuntime::FunctionCountMap HM;
|
||||
HM &h = rt->unjoinedFunctionCountMap;
|
||||
HM::AddPtr p = h.lookupForAdd(fun);
|
||||
if (!p) {
|
||||
h.add(p, fun, 1);
|
||||
} else {
|
||||
JS_ASSERT(p->key == fun);
|
||||
++p->value;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
parent = js_GetScopeChain(cx, fp);
|
||||
if (!parent)
|
||||
|
|
|
@ -181,14 +181,33 @@ struct JSStackFrame
|
|||
return argv[-2];
|
||||
}
|
||||
|
||||
/* Infallible getter to return the callee object from this frame. */
|
||||
JSObject &calleeObject() const {
|
||||
JS_ASSERT(argv);
|
||||
return argv[-2].toObject();
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallible getter to compute the correct callee function object, which may
|
||||
* require deferred cloning due to JSScope::methodReadBarrier. For a frame
|
||||
* with null fun member, return true with *vp set from this->calleeValue(),
|
||||
* which may not be an object (it could be undefined).
|
||||
*/
|
||||
bool getValidCalleeObject(JSContext *cx, js::Value *vp);
|
||||
|
||||
void setCalleeObject(JSObject &callable) {
|
||||
JS_ASSERT(argv);
|
||||
argv[-2].setObject(callable);
|
||||
}
|
||||
|
||||
JSObject *callee() {
|
||||
return argv ? &argv[-2].toObject() : NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the object associated with the Execution Context's
|
||||
* VariableEnvironment (ES5 10.3). The given CallStackSegment must contain
|
||||
* this stack frame.
|
||||
* Get the "variable object" (ES3 term) associated with the Execution
|
||||
* Context's VariableEnvironment (ES5 10.3). The given CallStackSegment
|
||||
* must contain this stack frame.
|
||||
*/
|
||||
JSObject *varobj(js::CallStackSegment *css) const;
|
||||
|
||||
|
|
|
@ -5079,13 +5079,13 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow,
|
|||
* Check for Object class here to avoid defining a method on a class
|
||||
* with magic resolve, addProperty, getProperty, etc. hooks.
|
||||
*/
|
||||
if ((defineHow & JSDNP_SET_METHOD) &&
|
||||
obj->getClass() == &js_ObjectClass) {
|
||||
if ((defineHow & JSDNP_SET_METHOD) && obj->canHaveMethodBarrier()) {
|
||||
JS_ASSERT(IsFunctionObject(*vp));
|
||||
JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
|
||||
|
||||
JSObject *funobj = &vp->toObject();
|
||||
if (FUN_OBJECT(GET_FUNCTION_PRIVATE(cx, funobj)) == funobj) {
|
||||
JSFunction *fun = GET_FUNCTION_PRIVATE(cx, funobj);
|
||||
if (fun == funobj) {
|
||||
flags |= JSScopeProperty::METHOD;
|
||||
getter = CastAsPropertyOp(funobj);
|
||||
}
|
||||
|
@ -5236,8 +5236,39 @@ js_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, Value *rval)
|
|||
}
|
||||
|
||||
scope = obj->scope();
|
||||
if (SPROP_HAS_VALID_SLOT(sprop, scope))
|
||||
GC_POKE(cx, obj->lockedGetSlot(sprop->slot));
|
||||
if (SPROP_HAS_VALID_SLOT(sprop, scope)) {
|
||||
const Value &v = obj->lockedGetSlot(sprop->slot);
|
||||
GC_POKE(cx, v);
|
||||
|
||||
/*
|
||||
* Delete is rare enough that we can take the hit of checking for an
|
||||
* active cloned method function object that must be homed to a callee
|
||||
* slot on the active stack frame before this delete completes, in case
|
||||
* someone saved the clone and checks it against foo.caller for a foo
|
||||
* called from the active method.
|
||||
*
|
||||
* We do not check suspended frames. They can't be reached via caller,
|
||||
* so the only way they could have the method's joined function object
|
||||
* as callee is through an API abusage. We break any such edge case.
|
||||
*/
|
||||
if (scope->hasMethodBarrier()) {
|
||||
JSObject *funobj;
|
||||
|
||||
if (IsFunctionObject(v, &funobj)) {
|
||||
JSFunction *fun = GET_FUNCTION_PRIVATE(cx, funobj);
|
||||
|
||||
if (fun != funobj) {
|
||||
for (JSStackFrame *fp = cx->fp; fp; fp = fp->down) {
|
||||
if (fp->callee() == fun &&
|
||||
fp->thisv.isObject() &&
|
||||
&fp->thisv.toObject() == obj) {
|
||||
fp->setCalleeObject(*funobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ok = scope->removeProperty(cx, id);
|
||||
JS_UNLOCK_OBJ(cx, obj);
|
||||
|
@ -6153,7 +6184,7 @@ dumpValue(const Value &v)
|
|||
Class *clasp = obj->getClass();
|
||||
fprintf(stderr, "<%s%s at %p>",
|
||||
clasp->name,
|
||||
clasp == &js_ObjectClass ? "" : " object",
|
||||
(clasp == &js_ObjectClass) ? "" : " object",
|
||||
(void *) obj);
|
||||
} else if (v.isBoolean()) {
|
||||
if (v.toBoolean())
|
||||
|
|
|
@ -106,6 +106,12 @@ JSObject::getReservedSlot(uintN index) const
|
|||
return (slot < numSlots()) ? getSlot(slot) : js::UndefinedValue();
|
||||
}
|
||||
|
||||
inline bool
|
||||
JSObject::canHaveMethodBarrier() const
|
||||
{
|
||||
return isObject() || isFunction() || isPrimitive() || isDate();
|
||||
}
|
||||
|
||||
inline bool
|
||||
JSObject::isPrimitive() const
|
||||
{
|
||||
|
@ -308,6 +314,19 @@ JSObject::setDateUTCTime(const js::Value &time)
|
|||
fslots[JSSLOT_DATE_UTC_TIME] = time;
|
||||
}
|
||||
|
||||
inline bool
|
||||
JSObject::hasMethodObj(const JSObject& obj) const
|
||||
{
|
||||
return fslots[JSSLOT_FUN_METHOD_OBJ].isObject() &&
|
||||
&fslots[JSSLOT_FUN_METHOD_OBJ].toObject() == &obj;
|
||||
}
|
||||
|
||||
inline void
|
||||
JSObject::setMethodObj(JSObject& obj)
|
||||
{
|
||||
fslots[JSSLOT_FUN_METHOD_OBJ].setObject(obj);
|
||||
}
|
||||
|
||||
inline NativeIterator *
|
||||
JSObject::getNativeIterator() const
|
||||
{
|
||||
|
|
|
@ -5495,10 +5495,6 @@ SimulateImacroCFG(JSContext *cx, JSScript *script,
|
|||
#undef LOCAL_ASSERT
|
||||
#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, -1);
|
||||
|
||||
static intN
|
||||
ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *target,
|
||||
jsbytecode **pcstack);
|
||||
|
||||
static intN
|
||||
ReconstructImacroPCStack(JSContext *cx, JSScript *script,
|
||||
jsbytecode *imacstart, jsbytecode *target,
|
||||
|
|
|
@ -2130,11 +2130,7 @@ DeoptimizeUsesWithin(JSDefinition *dn, JSFunctionBox *funbox, uint32& tcflags)
|
|||
void
|
||||
Parser::setFunctionKinds(JSFunctionBox *funbox, uint32& tcflags)
|
||||
{
|
||||
#ifdef JS_FUNCTION_METERING
|
||||
# define FUN_METER(x) JS_RUNTIME_METER(context->runtime, functionMeter.x)
|
||||
#else
|
||||
# define FUN_METER(x) ((void)0)
|
||||
#endif
|
||||
#define FUN_METER(x) JS_FUNCTION_METER(context, x)
|
||||
|
||||
for (;;) {
|
||||
JSParseNode *fn = funbox->node;
|
||||
|
@ -2344,6 +2340,9 @@ Parser::setFunctionKinds(JSFunctionBox *funbox, uint32& tcflags)
|
|||
}
|
||||
}
|
||||
|
||||
if (funbox->joinable())
|
||||
fun->setJoinable();
|
||||
|
||||
funbox = funbox->siblings;
|
||||
if (!funbox)
|
||||
break;
|
||||
|
|
|
@ -1211,7 +1211,7 @@ JSScope::methodShapeChange(JSContext *cx, JSScopeProperty *sprop)
|
|||
const Value &prev = object->lockedGetSlot(sprop->slot);
|
||||
JS_ASSERT(&sprop->methodObject() == &prev.toObject());
|
||||
JS_ASSERT(hasMethodBarrier());
|
||||
JS_ASSERT(object->getClass() == &js_ObjectClass);
|
||||
JS_ASSERT(object->canHaveMethodBarrier());
|
||||
JS_ASSERT(!sprop->rawSetter || sprop->rawSetter == js_watch_set);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -611,7 +611,9 @@ struct JSScopeProperty {
|
|||
union {
|
||||
js::PropertyOp rawGetter; /* getter and setter hooks or objects */
|
||||
JSObject *getterObj; /* user-defined callable "get" object or
|
||||
null if sprop->hasGetterValue() */
|
||||
null if sprop->hasGetterValue(); or
|
||||
joined function object if METHOD flag
|
||||
is set. */
|
||||
JSScopeProperty *next; /* next node in freelist */
|
||||
};
|
||||
|
||||
|
|
|
@ -122,17 +122,37 @@ JSScope::methodReadBarrier(JSContext *cx, JSScopeProperty *sprop, js::Value *vp)
|
|||
JS_ASSERT(hasProperty(sprop));
|
||||
JS_ASSERT(sprop->isMethod());
|
||||
JS_ASSERT(&vp->toObject() == &sprop->methodObject());
|
||||
JS_ASSERT(object->getClass() == &js_ObjectClass);
|
||||
JS_ASSERT(object->canHaveMethodBarrier());
|
||||
|
||||
JSObject *funobj = &vp->toObject();
|
||||
JSFunction *fun = GET_FUNCTION_PRIVATE(cx, funobj);
|
||||
JS_ASSERT(FUN_OBJECT(fun) == funobj && FUN_NULL_CLOSURE(fun));
|
||||
JS_ASSERT(fun == funobj && FUN_NULL_CLOSURE(fun));
|
||||
|
||||
funobj = CloneFunctionObject(cx, fun, funobj->getParent());
|
||||
if (!funobj)
|
||||
return false;
|
||||
funobj->setMethodObj(*object);
|
||||
|
||||
vp->setObject(*funobj);
|
||||
return !!js_SetPropertyHelper(cx, object, sprop->id, 0, vp);
|
||||
if (!js_SetPropertyHelper(cx, object, sprop->id, 0, vp))
|
||||
return false;
|
||||
|
||||
#ifdef DEBUG
|
||||
if (cx->runtime->functionMeterFilename) {
|
||||
JS_FUNCTION_METER(cx, mreadbarrier);
|
||||
|
||||
typedef JSRuntime::FunctionCountMap HM;
|
||||
HM &h = cx->runtime->methodReadBarrierCountMap;
|
||||
HM::AddPtr p = h.lookupForAdd(fun);
|
||||
if (!p) {
|
||||
h.add(p, fun, 1);
|
||||
} else {
|
||||
JS_ASSERT(p->key == fun);
|
||||
++p->value;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static JS_ALWAYS_INLINE bool
|
||||
|
@ -149,8 +169,10 @@ JSScope::methodWriteBarrier(JSContext *cx, JSScopeProperty *sprop,
|
|||
{
|
||||
if (flags & (BRANDED | METHOD_BARRIER)) {
|
||||
const js::Value &prev = object->lockedGetSlot(sprop->slot);
|
||||
if (ChangesMethodValue(prev, v))
|
||||
if (ChangesMethodValue(prev, v)) {
|
||||
JS_FUNCTION_METER(cx, mwritebarrier);
|
||||
return methodShapeChange(cx, sprop);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -160,8 +182,10 @@ JSScope::methodWriteBarrier(JSContext *cx, uint32 slot, const js::Value &v)
|
|||
{
|
||||
if (flags & (BRANDED | METHOD_BARRIER)) {
|
||||
const js::Value &prev = object->lockedGetSlot(slot);
|
||||
if (ChangesMethodValue(prev, v))
|
||||
if (ChangesMethodValue(prev, v)) {
|
||||
JS_FUNCTION_METER(cx, mwslotbarrier);
|
||||
return methodShapeChange(cx, slot);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -617,12 +617,14 @@ SaveScriptFilename(JSRuntime *rt, const char *filename, uint32 flags)
|
|||
sfp->flags |= flags;
|
||||
}
|
||||
|
||||
#ifdef JS_FUNCTION_METERING
|
||||
size_t len = strlen(sfe->filename);
|
||||
if (len >= sizeof rt->lastScriptFilename)
|
||||
len = sizeof rt->lastScriptFilename - 1;
|
||||
memcpy(rt->lastScriptFilename, sfe->filename, len);
|
||||
rt->lastScriptFilename[len] = '\0';
|
||||
#ifdef DEBUG
|
||||
if (rt->functionMeterFilename) {
|
||||
size_t len = strlen(sfe->filename);
|
||||
if (len >= sizeof rt->lastScriptFilename)
|
||||
len = sizeof rt->lastScriptFilename - 1;
|
||||
memcpy(rt->lastScriptFilename, sfe->filename, len);
|
||||
rt->lastScriptFilename[len] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
||||
return sfe;
|
||||
|
|
|
@ -2277,8 +2277,8 @@ BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr,
|
|||
return true;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
str_replace(JSContext *cx, uintN argc, Value *vp)
|
||||
JSBool
|
||||
js::str_replace(JSContext *cx, uintN argc, Value *vp)
|
||||
{
|
||||
ReplaceData rdata(cx);
|
||||
NORMALIZE_THIS(cx, vp, rdata.str);
|
||||
|
|
|
@ -1113,6 +1113,15 @@ extern JSBool
|
|||
js_str_escape(JSContext *cx, JSObject *obj, uintN argc, js::Value *argv,
|
||||
js::Value *rval);
|
||||
|
||||
/*
|
||||
* The String.prototype.replace fast-native entry point is exported for joined
|
||||
* function optimization in js{interp,tracer}.cpp.
|
||||
*/
|
||||
namespace js {
|
||||
extern JSBool
|
||||
str_replace(JSContext *cx, uintN argc, js::Value *vp);
|
||||
}
|
||||
|
||||
extern JSBool
|
||||
js_str_toString(JSContext *cx, uintN argc, js::Value *vp);
|
||||
|
||||
|
|
|
@ -14873,19 +14873,60 @@ TraceRecorder::record_JSOP_LAMBDA()
|
|||
if (FUN_NULL_CLOSURE(fun)) {
|
||||
if (FUN_OBJECT(fun)->getParent() != globalObj)
|
||||
RETURN_STOP_A("Null closure function object parent must be global object");
|
||||
JSOp op2 = JSOp(cx->regs->pc[JSOP_LAMBDA_LENGTH]);
|
||||
|
||||
jsbytecode *pc2 = cx->regs->pc + JSOP_LAMBDA_LENGTH;
|
||||
JSOp op2 = JSOp(*pc2);
|
||||
|
||||
if (op2 == JSOP_INITMETHOD) {
|
||||
stack(0, INS_CONSTOBJ(FUN_OBJECT(fun)));
|
||||
return ARECORD_CONTINUE;
|
||||
}
|
||||
|
||||
if (op2 == JSOP_SETMETHOD) {
|
||||
Value lval = stackval(-1);
|
||||
|
||||
if (!lval.isPrimitive() &&
|
||||
lval.toObject().getClass() == &js_ObjectClass) {
|
||||
if (!lval.isPrimitive() && lval.toObject().canHaveMethodBarrier()) {
|
||||
stack(0, INS_CONSTOBJ(FUN_OBJECT(fun)));
|
||||
return ARECORD_CONTINUE;
|
||||
}
|
||||
} else if (op2 == JSOP_INITMETHOD) {
|
||||
stack(0, INS_CONSTOBJ(FUN_OBJECT(fun)));
|
||||
return ARECORD_CONTINUE;
|
||||
} else if (fun->joinable()) {
|
||||
if (op2 == JSOP_CALL) {
|
||||
/*
|
||||
* Array.prototype.sort and String.prototype.replace are
|
||||
* optimized as if they are special form. We know that they
|
||||
* won't leak the joined function object in obj, therefore
|
||||
* we don't need to clone that compiler- created function
|
||||
* object for identity/mutation reasons.
|
||||
*/
|
||||
int iargc = GET_ARGC(pc2);
|
||||
|
||||
/*
|
||||
* Note that we have not yet pushed obj as the final argument,
|
||||
* so regs.sp[1 - (iargc + 2)], and not regs.sp[-(iargc + 2)],
|
||||
* is the callee for this JSOP_CALL.
|
||||
*/
|
||||
const Value &cref = cx->regs->sp[1 - (iargc + 2)];
|
||||
JSObject *callee;
|
||||
|
||||
if (IsFunctionObject(cref, &callee)) {
|
||||
JSFunction *calleeFun = GET_FUNCTION_PRIVATE(cx, callee);
|
||||
FastNative fastNative = FUN_FAST_NATIVE(calleeFun);
|
||||
|
||||
if ((iargc == 1 && fastNative == array_sort) ||
|
||||
(iargc == 2 && fastNative == str_replace)) {
|
||||
stack(0, INS_CONSTOBJ(FUN_OBJECT(fun)));
|
||||
return ARECORD_CONTINUE;
|
||||
}
|
||||
}
|
||||
} else if (op2 == JSOP_NULL) {
|
||||
pc2 += JSOP_NULL_LENGTH;
|
||||
op2 = JSOp(*pc2);
|
||||
|
||||
if (op2 == JSOP_CALL && GET_ARGC(pc2) == 0) {
|
||||
stack(0, INS_CONSTOBJ(FUN_OBJECT(fun)));
|
||||
return ARECORD_CONTINUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LIns *proto_ins;
|
||||
|
|
|
@ -598,7 +598,7 @@ class Value
|
|||
*
|
||||
* Private setters/getters allow the caller to read/write arbitrary types
|
||||
* that fit in the 64-bit payload. It is the caller's responsibility, after
|
||||
* storing to a value with setPrivateX to only read with getPrivateX.
|
||||
* storing to a value with setPrivateX to read only using getPrivateX.
|
||||
* Privates values are given a type type which ensures they are not marked.
|
||||
*/
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ skip-if(xulRuntime.OS=="WINNT"&&isDebugBuild) script regress-341360.js # slow
|
|||
script regress-343713.js
|
||||
script regress-343966.js
|
||||
script regress-344711-n.js
|
||||
random-if(!xulRuntime.shell&&xulRuntime.OS=="WINNT") asserts-if(!xulRuntime.shell&&xulRuntime.OS=="WINNT",1) script regress-344804.js # bug 524732
|
||||
skip script regress-344804.js # bug 524732
|
||||
script regress-344959.js
|
||||
script regress-346237.js
|
||||
script regress-346801.js
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
url-prefix ../../jsreftest.html?test=js1_5/Scope/
|
||||
script regress-154693.js
|
||||
random-if(!xulRuntime.shell&&xulRuntime.OS=="WINNT") asserts-if(!xulRuntime.shell&&xulRuntime.OS=="WINNT",1) script regress-181834.js # bug 524732
|
||||
skip script regress-181834.js # bug 524732
|
||||
script regress-184107.js
|
||||
script regress-185485.js
|
||||
script regress-191276.js
|
||||
|
|
|
@ -24,4 +24,6 @@ script regress-566914.js
|
|||
script regress-567152.js
|
||||
script regress-569306.js
|
||||
script regress-571014.js
|
||||
script regress-577648-1.js
|
||||
script regress-577648-2.js
|
||||
script regress-583429.js
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/licenses/publicdomain/
|
||||
*/
|
||||
|
||||
var count = 0;
|
||||
|
||||
function testCaller(obj) {
|
||||
switch (++count) {
|
||||
case 1:
|
||||
case 2:
|
||||
/*
|
||||
* The first two times, obj is objA. The first time, we reference
|
||||
* arguments.callee.caller before obj.go, so the caller getter must
|
||||
* force the joined function object in the stack frame to cross the
|
||||
* method read barrier. The second time, obj.go has been cloned and
|
||||
* it should match the new frame's callee from the get-go.
|
||||
*/
|
||||
assertEq(obj, objA);
|
||||
break;
|
||||
|
||||
case 3: {
|
||||
assertEq(obj, objB);
|
||||
|
||||
/*
|
||||
* Store another clone of the joined function object before obj.go has
|
||||
* been read, but after it has been invoked via objB.go(objB).
|
||||
*
|
||||
* In this case, arguments.callee.caller must not lie and return what
|
||||
* is currently stored in objB.go, since that function object (objA.go)
|
||||
* was cloned earlier, when count was 1, and it is not the function
|
||||
* object that was truly invoked.
|
||||
*
|
||||
* But since the invocation of objB.go(objB) did not clone go, and the
|
||||
* following assignment overwrote the invoked value, leaving the only
|
||||
* reference to the joined function object for go in the stack frame's
|
||||
* callee (argv[-2]) member, the arguments.callee.caller reference must
|
||||
* clone a function object for the callee, store it as the callee, and
|
||||
* return it here.
|
||||
*
|
||||
* It won't equal obj.go, but (implementation detail) it should have
|
||||
* the same proto as obj.go
|
||||
*/
|
||||
obj.go = objA.go;
|
||||
|
||||
let caller = arguments.callee.caller;
|
||||
let obj_go = obj.go;
|
||||
return caller != obj_go && caller.__proto__ == obj_go.__proto__;
|
||||
}
|
||||
|
||||
case 4: {
|
||||
assertEq(obj, objC);
|
||||
|
||||
let save = obj.go;
|
||||
delete obj.go;
|
||||
return arguments.callee.caller == save;
|
||||
}
|
||||
|
||||
case 5: {
|
||||
assertEq(obj, objD);
|
||||
|
||||
let read = obj.go;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return arguments.callee.caller == obj.go;
|
||||
}
|
||||
|
||||
function make() {
|
||||
return {
|
||||
go: function(obj) {
|
||||
return testCaller(obj);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var objA = make(),
|
||||
objB = make(),
|
||||
objC = make(),
|
||||
objD = make();
|
||||
|
||||
reportCompare(true, objA.go(objA), "1");
|
||||
reportCompare(true, objA.go(objA), "2");
|
||||
reportCompare(true, objB.go(objB), "3");
|
||||
reportCompare(true, objC.go(objC), "4");
|
||||
reportCompare(true, objD.go(objD), "5");
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/licenses/publicdomain/
|
||||
* Contributor: Jason Orendorff
|
||||
*/
|
||||
|
||||
var o = { f: function() { return o.g(); }, g: function() { return arguments.callee.caller; } };
|
||||
var c = o.f();
|
||||
var i = 'f';
|
||||
var d = o[i]();
|
||||
|
||||
reportCompare(true, c === o.f && d === o.f(), "");
|
|
@ -0,0 +1,5 @@
|
|||
for (b = 0; b < 2; b++) {
|
||||
/ /
|
||||
(function(){})
|
||||
}
|
||||
|
Загрузка…
Ссылка в новой задаче