1 |
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 |
* vim: set ts=8 sw=4 et tw=80: |
3 |
* |
4 |
* ***** BEGIN LICENSE BLOCK ***** |
5 |
* Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
6 |
* |
7 |
* The contents of this file are subject to the Mozilla Public License Version |
8 |
* 1.1 (the "License"); you may not use this file except in compliance with |
9 |
* the License. You may obtain a copy of the License at |
10 |
* http://www.mozilla.org/MPL/ |
11 |
* |
12 |
* Software distributed under the License is distributed on an "AS IS" basis, |
13 |
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
14 |
* for the specific language governing rights and limitations under the |
15 |
* License. |
16 |
* |
17 |
* The Original Code is Mozilla Communicator client code, released |
18 |
* March 31, 1998. |
19 |
* |
20 |
* The Initial Developer of the Original Code is |
21 |
* Netscape Communications Corporation. |
22 |
* Portions created by the Initial Developer are Copyright (C) 1998 |
23 |
* the Initial Developer. All Rights Reserved. |
24 |
* |
25 |
* Contributor(s): |
26 |
* |
27 |
* Alternatively, the contents of this file may be used under the terms of |
28 |
* either of the GNU General Public License Version 2 or later (the "GPL"), |
29 |
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
30 |
* in which case the provisions of the GPL or the LGPL are applicable instead |
31 |
* of those above. If you wish to allow use of your version of this file only |
32 |
* under the terms of either the GPL or the LGPL, and not to allow others to |
33 |
* use your version of this file under the terms of the MPL, indicate your |
34 |
* decision by deleting the provisions above and replace them with the notice |
35 |
* and other provisions required by the GPL or the LGPL. If you do not delete |
36 |
* the provisions above, a recipient may use your version of this file under |
37 |
* the terms of any one of the MPL, the GPL or the LGPL. |
38 |
* |
39 |
* ***** END LICENSE BLOCK ***** */ |
40 |
|
41 |
/* |
42 |
* JS execution context. |
43 |
*/ |
44 |
#include "jsstddef.h" |
45 |
#include <stdarg.h> |
46 |
#include <stdlib.h> |
47 |
#include <string.h> |
48 |
#include "jstypes.h" |
49 |
#include "jsarena.h" /* Added by JSIFY */ |
50 |
#include "jsutil.h" /* Added by JSIFY */ |
51 |
#include "jsclist.h" |
52 |
#include "jsprf.h" |
53 |
#include "jsatom.h" |
54 |
#include "jscntxt.h" |
55 |
#include "jsversion.h" |
56 |
#include "jsdbgapi.h" |
57 |
#include "jsexn.h" |
58 |
#include "jsfun.h" |
59 |
#include "jsgc.h" |
60 |
#include "jslock.h" |
61 |
#include "jsnum.h" |
62 |
#include "jsobj.h" |
63 |
#include "jsopcode.h" |
64 |
#include "jsscan.h" |
65 |
#include "jsscope.h" |
66 |
#include "jsscript.h" |
67 |
#include "jsstr.h" |
68 |
#include "jstracer.h" |
69 |
|
70 |
#ifdef JS_THREADSAFE |
71 |
#include "prtypes.h" |
72 |
|
73 |
/* |
74 |
* The index for JSThread info, returned by PR_NewThreadPrivateIndex. The |
75 |
* index value is visible and shared by all threads, but the data associated |
76 |
* with it is private to each thread. |
77 |
*/ |
78 |
static PRUintn threadTPIndex; |
79 |
static JSBool tpIndexInited = JS_FALSE; |
80 |
|
81 |
JS_BEGIN_EXTERN_C |
82 |
JSBool |
83 |
js_InitThreadPrivateIndex(void (*ptr)(void *)) |
84 |
{ |
85 |
PRStatus status; |
86 |
|
87 |
if (tpIndexInited) |
88 |
return JS_TRUE; |
89 |
|
90 |
status = PR_NewThreadPrivateIndex(&threadTPIndex, ptr); |
91 |
|
92 |
if (status == PR_SUCCESS) |
93 |
tpIndexInited = JS_TRUE; |
94 |
return status == PR_SUCCESS; |
95 |
} |
96 |
JS_END_EXTERN_C |
97 |
|
98 |
/* |
99 |
* Callback function to delete a JSThread info when the thread that owns it |
100 |
* is destroyed. |
101 |
*/ |
102 |
void |
103 |
js_ThreadDestructorCB(void *ptr) |
104 |
{ |
105 |
JSThread *thread = (JSThread *)ptr; |
106 |
|
107 |
if (!thread) |
108 |
return; |
109 |
|
110 |
/* |
111 |
* Check that this thread properly called either JS_DestroyContext or |
112 |
* JS_ClearContextThread on each JSContext it created or used. |
113 |
*/ |
114 |
JS_ASSERT(JS_CLIST_IS_EMPTY(&thread->contextList)); |
115 |
GSN_CACHE_CLEAR(&thread->gsnCache); |
116 |
#if defined JS_TRACER |
117 |
js_FinishJIT(&thread->traceMonitor); |
118 |
#endif |
119 |
free(thread); |
120 |
} |
121 |
|
122 |
/* |
123 |
* Get current thread-local JSThread info, creating one if it doesn't exist. |
124 |
* Each thread has a unique JSThread pointer. |
125 |
* |
126 |
* Since we are dealing with thread-local data, no lock is needed. |
127 |
* |
128 |
* Return a pointer to the thread local info, NULL if the system runs out |
129 |
* of memory, or it failed to set thread private data (neither case is very |
130 |
* likely; both are probably due to out-of-memory). It is up to the caller |
131 |
* to report an error, if possible. |
132 |
*/ |
133 |
JSThread * |
134 |
js_GetCurrentThread(JSRuntime *rt) |
135 |
{ |
136 |
JSThread *thread; |
137 |
|
138 |
thread = (JSThread *)PR_GetThreadPrivate(threadTPIndex); |
139 |
if (!thread) { |
140 |
thread = (JSThread *) malloc(sizeof(JSThread)); |
141 |
if (!thread) |
142 |
return NULL; |
143 |
#ifdef DEBUG |
144 |
memset(thread, JS_FREE_PATTERN, sizeof(JSThread)); |
145 |
#endif |
146 |
if (PR_FAILURE == PR_SetThreadPrivate(threadTPIndex, thread)) { |
147 |
free(thread); |
148 |
return NULL; |
149 |
} |
150 |
|
151 |
JS_INIT_CLIST(&thread->contextList); |
152 |
thread->id = js_CurrentThreadId(); |
153 |
thread->gcMallocBytes = 0; |
154 |
#ifdef JS_TRACER |
155 |
memset(&thread->traceMonitor, 0, sizeof(thread->traceMonitor)); |
156 |
js_InitJIT(&thread->traceMonitor); |
157 |
#endif |
158 |
thread->scriptsToGC = NULL; |
159 |
|
160 |
/* |
161 |
* js_SetContextThread initializes the remaining fields as necessary. |
162 |
*/ |
163 |
} |
164 |
return thread; |
165 |
} |
166 |
|
167 |
/* |
168 |
* Sets current thread as owning thread of a context by assigning the |
169 |
* thread-private info to the context. If the current thread doesn't have |
170 |
* private JSThread info, create one. |
171 |
*/ |
172 |
JSBool |
173 |
js_SetContextThread(JSContext *cx) |
174 |
{ |
175 |
JSThread *thread = js_GetCurrentThread(cx->runtime); |
176 |
|
177 |
if (!thread) { |
178 |
JS_ReportOutOfMemory(cx); |
179 |
return JS_FALSE; |
180 |
} |
181 |
|
182 |
/* |
183 |
* Clear caches on each transition from 0 to 1 context active on the |
184 |
* current thread. See bug 425828. |
185 |
*/ |
186 |
if (JS_CLIST_IS_EMPTY(&thread->contextList)) { |
187 |
memset(&thread->gsnCache, 0, sizeof(thread->gsnCache)); |
188 |
memset(&thread->propertyCache, 0, sizeof(thread->propertyCache)); |
189 |
} |
190 |
|
191 |
/* Assert that the previous cx->thread called JS_ClearContextThread(). */ |
192 |
JS_ASSERT(!cx->thread || cx->thread == thread); |
193 |
if (!cx->thread) |
194 |
JS_APPEND_LINK(&cx->threadLinks, &thread->contextList); |
195 |
cx->thread = thread; |
196 |
return JS_TRUE; |
197 |
} |
198 |
|
199 |
/* Remove the owning thread info of a context. */ |
200 |
void |
201 |
js_ClearContextThread(JSContext *cx) |
202 |
{ |
203 |
/* |
204 |
* If cx is associated with a thread, this must be called only from that |
205 |
* thread. If not, this is a harmless no-op. |
206 |
*/ |
207 |
JS_ASSERT(cx->thread == js_GetCurrentThread(cx->runtime) || !cx->thread); |
208 |
JS_REMOVE_AND_INIT_LINK(&cx->threadLinks); |
209 |
cx->thread = NULL; |
210 |
} |
211 |
|
212 |
#endif /* JS_THREADSAFE */ |
213 |
|
214 |
void |
215 |
js_OnVersionChange(JSContext *cx) |
216 |
{ |
217 |
#ifdef DEBUG |
218 |
JSVersion version = JSVERSION_NUMBER(cx); |
219 |
|
220 |
JS_ASSERT(version == JSVERSION_DEFAULT || version >= JSVERSION_ECMA_3); |
221 |
#endif |
222 |
} |
223 |
|
224 |
void |
225 |
js_SetVersion(JSContext *cx, JSVersion version) |
226 |
{ |
227 |
cx->version = version; |
228 |
js_OnVersionChange(cx); |
229 |
} |
230 |
|
231 |
JSContext * |
232 |
js_NewContext(JSRuntime *rt, size_t stackChunkSize) |
233 |
{ |
234 |
JSContext *cx; |
235 |
JSBool ok, first; |
236 |
JSContextCallback cxCallback; |
237 |
|
238 |
cx = (JSContext *) malloc(sizeof *cx); |
239 |
if (!cx) |
240 |
return NULL; |
241 |
memset(cx, 0, sizeof *cx); |
242 |
|
243 |
cx->runtime = rt; |
244 |
JS_ClearOperationCallback(cx); |
245 |
cx->debugHooks = &rt->globalDebugHooks; |
246 |
#if JS_STACK_GROWTH_DIRECTION > 0 |
247 |
cx->stackLimit = (jsuword)-1; |
248 |
#endif |
249 |
cx->scriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA; |
250 |
#ifdef JS_THREADSAFE |
251 |
cx->gcLocalFreeLists = (JSGCFreeListSet *) &js_GCEmptyFreeListSet; |
252 |
JS_INIT_CLIST(&cx->threadLinks); |
253 |
js_SetContextThread(cx); |
254 |
#endif |
255 |
|
256 |
JS_LOCK_GC(rt); |
257 |
for (;;) { |
258 |
first = (rt->contextList.next == &rt->contextList); |
259 |
if (rt->state == JSRTS_UP) { |
260 |
JS_ASSERT(!first); |
261 |
break; |
262 |
} |
263 |
if (rt->state == JSRTS_DOWN) { |
264 |
JS_ASSERT(first); |
265 |
rt->state = JSRTS_LAUNCHING; |
266 |
break; |
267 |
} |
268 |
JS_WAIT_CONDVAR(rt->stateChange, JS_NO_TIMEOUT); |
269 |
} |
270 |
JS_APPEND_LINK(&cx->links, &rt->contextList); |
271 |
JS_UNLOCK_GC(rt); |
272 |
|
273 |
/* |
274 |
* First we do the infallible, every-time per-context initializations. |
275 |
* Should a later, fallible initialization (js_InitRegExpStatics, e.g., |
276 |
* or the stuff under 'if (first)' below) fail, at least the version |
277 |
* and arena-pools will be valid and safe to use (say, from the last GC |
278 |
* done by js_DestroyContext). |
279 |
*/ |
280 |
cx->version = JSVERSION_DEFAULT; |
281 |
JS_INIT_ARENA_POOL(&cx->stackPool, "stack", stackChunkSize, sizeof(jsval), |
282 |
&cx->scriptStackQuota); |
283 |
|
284 |
JS_INIT_ARENA_POOL(&cx->tempPool, "temp", |
285 |
1024, /* FIXME: bug 421435 */ |
286 |
sizeof(jsdouble), &cx->scriptStackQuota); |
287 |
|
288 |
/* |
289 |
* To avoid multiple allocations in InitMatch() (in jsregexp.c), the arena |
290 |
* size parameter should be at least as big as: |
291 |
* INITIAL_BACKTRACK |
292 |
* + (sizeof(REProgState) * INITIAL_STATESTACK) |
293 |
* + (offsetof(REMatchState, parens) + avgParanSize * sizeof(RECapture)) |
294 |
*/ |
295 |
JS_INIT_ARENA_POOL(&cx->regexpPool, "regexp", |
296 |
12 * 1024 - 40, /* FIXME: bug 421435 */ |
297 |
sizeof(void *), &cx->scriptStackQuota); |
298 |
|
299 |
if (!js_InitRegExpStatics(cx, &cx->regExpStatics)) { |
300 |
js_DestroyContext(cx, JSDCM_NEW_FAILED); |
301 |
return NULL; |
302 |
} |
303 |
|
304 |
cx->resolveFlags = 0; |
305 |
|
306 |
/* |
307 |
* If cx is the first context on this runtime, initialize well-known atoms, |
308 |
* keywords, numbers, and strings. If one of these steps should fail, the |
309 |
* runtime will be left in a partially initialized state, with zeroes and |
310 |
* nulls stored in the default-initialized remainder of the struct. We'll |
311 |
* clean the runtime up under js_DestroyContext, because cx will be "last" |
312 |
* as well as "first". |
313 |
*/ |
314 |
if (first) { |
315 |
#ifdef JS_THREADSAFE |
316 |
JS_BeginRequest(cx); |
317 |
#endif |
318 |
ok = js_InitCommonAtoms(cx); |
319 |
|
320 |
/* |
321 |
* scriptFilenameTable may be left over from a previous episode of |
322 |
* non-zero contexts alive in rt, so don't re-init the table if it's |
323 |
* not necessary. |
324 |
*/ |
325 |
if (ok && !rt->scriptFilenameTable) |
326 |
ok = js_InitRuntimeScriptState(rt); |
327 |
if (ok) |
328 |
ok = js_InitRuntimeNumberState(cx); |
329 |
if (ok) |
330 |
ok = js_InitRuntimeStringState(cx); |
331 |
#ifdef JS_THREADSAFE |
332 |
JS_EndRequest(cx); |
333 |
#endif |
334 |
if (!ok) { |
335 |
js_DestroyContext(cx, JSDCM_NEW_FAILED); |
336 |
return NULL; |
337 |
} |
338 |
|
339 |
JS_LOCK_GC(rt); |
340 |
rt->state = JSRTS_UP; |
341 |
JS_NOTIFY_ALL_CONDVAR(rt->stateChange); |
342 |
JS_UNLOCK_GC(rt); |
343 |
} |
344 |
|
345 |
cxCallback = rt->cxCallback; |
346 |
if (cxCallback && !cxCallback(cx, JSCONTEXT_NEW)) { |
347 |
js_DestroyContext(cx, JSDCM_NEW_FAILED); |
348 |
return NULL; |
349 |
} |
350 |
|
351 |
return cx; |
352 |
} |
353 |
|
354 |
void |
355 |
js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) |
356 |
{ |
357 |
JSRuntime *rt; |
358 |
JSContextCallback cxCallback; |
359 |
JSBool last; |
360 |
JSArgumentFormatMap *map; |
361 |
JSLocalRootStack *lrs; |
362 |
JSLocalRootChunk *lrc; |
363 |
|
364 |
rt = cx->runtime; |
365 |
|
366 |
if (mode != JSDCM_NEW_FAILED) { |
367 |
cxCallback = rt->cxCallback; |
368 |
if (cxCallback) { |
369 |
/* |
370 |
* JSCONTEXT_DESTROY callback is not allowed to fail and must |
371 |
* return true. |
372 |
*/ |
373 |
#ifdef DEBUG |
374 |
JSBool callbackStatus = |
375 |
#endif |
376 |
cxCallback(cx, JSCONTEXT_DESTROY); |
377 |
JS_ASSERT(callbackStatus); |
378 |
} |
379 |
} |
380 |
|
381 |
/* Remove cx from context list first. */ |
382 |
JS_LOCK_GC(rt); |
383 |
JS_ASSERT(rt->state == JSRTS_UP || rt->state == JSRTS_LAUNCHING); |
384 |
JS_REMOVE_LINK(&cx->links); |
385 |
last = (rt->contextList.next == &rt->contextList); |
386 |
if (last) |
387 |
rt->state = JSRTS_LANDING; |
388 |
#ifdef JS_THREADSAFE |
389 |
js_RevokeGCLocalFreeLists(cx); |
390 |
#endif |
391 |
JS_UNLOCK_GC(rt); |
392 |
|
393 |
if (last) { |
394 |
#ifdef JS_THREADSAFE |
395 |
/* |
396 |
* If cx is not in a request already, begin one now so that we wait |
397 |
* for any racing GC started on a not-last context to finish, before |
398 |
* we plow ahead and unpin atoms. Note that even though we begin a |
399 |
* request here if necessary, we end all requests on cx below before |
400 |
* forcing a final GC. This lets any not-last context destruction |
401 |
* racing in another thread try to force or maybe run the GC, but by |
402 |
* that point, rt->state will not be JSRTS_UP, and that GC attempt |
403 |
* will return early. |
404 |
*/ |
405 |
if (cx->requestDepth == 0) |
406 |
JS_BeginRequest(cx); |
407 |
#endif |
408 |
|
409 |
/* Unlock and clear GC things held by runtime pointers. */ |
410 |
js_FinishRuntimeNumberState(cx); |
411 |
js_FinishRuntimeStringState(cx); |
412 |
|
413 |
/* Unpin all common atoms before final GC. */ |
414 |
js_FinishCommonAtoms(cx); |
415 |
|
416 |
/* Clear debugging state to remove GC roots. */ |
417 |
JS_ClearAllTraps(cx); |
418 |
JS_ClearAllWatchPoints(cx); |
419 |
} |
420 |
|
421 |
/* |
422 |
* Remove more GC roots in regExpStatics, then collect garbage. |
423 |
* XXX anti-modularity alert: we rely on the call to js_RemoveRoot within |
424 |
* XXX this function call to wait for any racing GC to complete, in the |
425 |
* XXX case where JS_DestroyContext is called outside of a request on cx |
426 |
*/ |
427 |
js_FreeRegExpStatics(cx, &cx->regExpStatics); |
428 |
|
429 |
#ifdef JS_THREADSAFE |
430 |
/* |
431 |
* Destroying a context implicitly calls JS_EndRequest(). Also, we must |
432 |
* end our request here in case we are "last" -- in that event, another |
433 |
* js_DestroyContext that was not last might be waiting in the GC for our |
434 |
* request to end. We'll let it run below, just before we do the truly |
435 |
* final GC and then free atom state. |
436 |
* |
437 |
* At this point, cx must be inaccessible to other threads. It's off the |
438 |
* rt->contextList, and it should not be reachable via any object private |
439 |
* data structure. |
440 |
*/ |
441 |
while (cx->requestDepth != 0) |
442 |
JS_EndRequest(cx); |
443 |
#endif |
444 |
|
445 |
if (last) { |
446 |
js_GC(cx, GC_LAST_CONTEXT); |
447 |
|
448 |
/* |
449 |
* Free the script filename table if it exists and is empty. Do this |
450 |
* after the last GC to avoid finalizers tripping on free memory. |
451 |
*/ |
452 |
if (rt->scriptFilenameTable && rt->scriptFilenameTable->nentries == 0) |
453 |
js_FinishRuntimeScriptState(rt); |
454 |
|
455 |
/* Take the runtime down, now that it has no contexts or atoms. */ |
456 |
JS_LOCK_GC(rt); |
457 |
rt->state = JSRTS_DOWN; |
458 |
JS_NOTIFY_ALL_CONDVAR(rt->stateChange); |
459 |
JS_UNLOCK_GC(rt); |
460 |
} else { |
461 |
if (mode == JSDCM_FORCE_GC) |
462 |
js_GC(cx, GC_NORMAL); |
463 |
else if (mode == JSDCM_MAYBE_GC) |
464 |
JS_MaybeGC(cx); |
465 |
} |
466 |
|
467 |
/* Free the stuff hanging off of cx. */ |
468 |
JS_FinishArenaPool(&cx->stackPool); |
469 |
JS_FinishArenaPool(&cx->tempPool); |
470 |
JS_FinishArenaPool(&cx->regexpPool); |
471 |
|
472 |
if (cx->lastMessage) |
473 |
free(cx->lastMessage); |
474 |
|
475 |
/* Remove any argument formatters. */ |
476 |
map = cx->argumentFormatMap; |
477 |
while (map) { |
478 |
JSArgumentFormatMap *temp = map; |
479 |
map = map->next; |
480 |
JS_free(cx, temp); |
481 |
} |
482 |
|
483 |
/* Destroy the resolve recursion damper. */ |
484 |
if (cx->resolvingTable) { |
485 |
JS_DHashTableDestroy(cx->resolvingTable); |
486 |
cx->resolvingTable = NULL; |
487 |
} |
488 |
|
489 |
lrs = cx->localRootStack; |
490 |
if (lrs) { |
491 |
while ((lrc = lrs->topChunk) != &lrs->firstChunk) { |
492 |
lrs->topChunk = lrc->down; |
493 |
JS_free(cx, lrc); |
494 |
} |
495 |
JS_free(cx, lrs); |
496 |
} |
497 |
|
498 |
#ifdef JS_THREADSAFE |
499 |
js_ClearContextThread(cx); |
500 |
#endif |
501 |
|
502 |
/* Finally, free cx itself. */ |
503 |
free(cx); |
504 |
} |
505 |
|
506 |
JSBool |
507 |
js_ValidContextPointer(JSRuntime *rt, JSContext *cx) |
508 |
{ |
509 |
JSCList *cl; |
510 |
|
511 |
for (cl = rt->contextList.next; cl != &rt->contextList; cl = cl->next) { |
512 |
if (cl == &cx->links) |
513 |
return JS_TRUE; |
514 |
} |
515 |
JS_RUNTIME_METER(rt, deadContexts); |
516 |
return JS_FALSE; |
517 |
} |
518 |
|
519 |
JSContext * |
520 |
js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp) |
521 |
{ |
522 |
JSContext *cx = *iterp; |
523 |
|
524 |
if (unlocked) |
525 |
JS_LOCK_GC(rt); |
526 |
cx = (JSContext *) (cx ? cx->links.next : rt->contextList.next); |
527 |
if (&cx->links == &rt->contextList) |
528 |
cx = NULL; |
529 |
*iterp = cx; |
530 |
if (unlocked) |
531 |
JS_UNLOCK_GC(rt); |
532 |
return cx; |
533 |
} |
534 |
|
535 |
static JSDHashNumber |
536 |
resolving_HashKey(JSDHashTable *table, const void *ptr) |
537 |
{ |
538 |
const JSResolvingKey *key = (const JSResolvingKey *)ptr; |
539 |
|
540 |
return ((JSDHashNumber)JS_PTR_TO_UINT32(key->obj) >> JSVAL_TAGBITS) ^ key->id; |
541 |
} |
542 |
|
543 |
JS_PUBLIC_API(JSBool) |
544 |
resolving_MatchEntry(JSDHashTable *table, |
545 |
const JSDHashEntryHdr *hdr, |
546 |
const void *ptr) |
547 |
{ |
548 |
const JSResolvingEntry *entry = (const JSResolvingEntry *)hdr; |
549 |
const JSResolvingKey *key = (const JSResolvingKey *)ptr; |
550 |
|
551 |
return entry->key.obj == key->obj && entry->key.id == key->id; |
552 |
} |
553 |
|
554 |
static const JSDHashTableOps resolving_dhash_ops = { |
555 |
JS_DHashAllocTable, |
556 |
JS_DHashFreeTable, |
557 |
resolving_HashKey, |
558 |
resolving_MatchEntry, |
559 |
JS_DHashMoveEntryStub, |
560 |
JS_DHashClearEntryStub, |
561 |
JS_DHashFinalizeStub, |
562 |
NULL |
563 |
}; |
564 |
|
565 |
JSBool |
566 |
js_StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag, |
567 |
JSResolvingEntry **entryp) |
568 |
{ |
569 |
JSDHashTable *table; |
570 |
JSResolvingEntry *entry; |
571 |
|
572 |
table = cx->resolvingTable; |
573 |
if (!table) { |
574 |
table = JS_NewDHashTable(&resolving_dhash_ops, NULL, |
575 |
sizeof(JSResolvingEntry), |
576 |
JS_DHASH_MIN_SIZE); |
577 |
if (!table) |
578 |
goto outofmem; |
579 |
cx->resolvingTable = table; |
580 |
} |
581 |
|
582 |
entry = (JSResolvingEntry *) |
583 |
JS_DHashTableOperate(table, key, JS_DHASH_ADD); |
584 |
if (!entry) |
585 |
goto outofmem; |
586 |
|
587 |
if (entry->flags & flag) { |
588 |
/* An entry for (key, flag) exists already -- dampen recursion. */ |
589 |
entry = NULL; |
590 |
} else { |
591 |
/* Fill in key if we were the first to add entry, then set flag. */ |
592 |
if (!entry->key.obj) |
593 |
entry->key = *key; |
594 |
entry->flags |= flag; |
595 |
} |
596 |
*entryp = entry; |
597 |
return JS_TRUE; |
598 |
|
599 |
outofmem: |
600 |
JS_ReportOutOfMemory(cx); |
601 |
return JS_FALSE; |
602 |
} |
603 |
|
604 |
void |
605 |
js_StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag, |
606 |
JSResolvingEntry *entry, uint32 generation) |
607 |
{ |
608 |
JSDHashTable *table; |
609 |
|
610 |
/* |
611 |
* Clear flag from entry->flags and return early if other flags remain. |
612 |
* We must take care to re-lookup entry if the table has changed since |
613 |
* it was found by js_StartResolving. |
614 |
*/ |
615 |
table = cx->resolvingTable; |
616 |
if (!entry || table->generation != generation) { |
617 |
entry = (JSResolvingEntry *) |
618 |
JS_DHashTableOperate(table, key, JS_DHASH_LOOKUP); |
619 |
} |
620 |
JS_ASSERT(JS_DHASH_ENTRY_IS_BUSY(&entry->hdr)); |
621 |
entry->flags &= ~flag; |
622 |
if (entry->flags) |
623 |
return; |
624 |
|
625 |
/* |
626 |
* Do a raw remove only if fewer entries were removed than would cause |
627 |
* alpha to be less than .5 (alpha is at most .75). Otherwise, we just |
628 |
* call JS_DHashTableOperate to re-lookup the key and remove its entry, |
629 |
* compressing or shrinking the table as needed. |
630 |
*/ |
631 |
if (table->removedCount < JS_DHASH_TABLE_SIZE(table) >> 2) |
632 |
JS_DHashTableRawRemove(table, &entry->hdr); |
633 |
else |
634 |
JS_DHashTableOperate(table, key, JS_DHASH_REMOVE); |
635 |
} |
636 |
|
637 |
JSBool |
638 |
js_EnterLocalRootScope(JSContext *cx) |
639 |
{ |
640 |
JSLocalRootStack *lrs; |
641 |
int mark; |
642 |
|
643 |
lrs = cx->localRootStack; |
644 |
if (!lrs) { |
645 |
lrs = (JSLocalRootStack *) JS_malloc(cx, sizeof *lrs); |
646 |
if (!lrs) |
647 |
return JS_FALSE; |
648 |
lrs->scopeMark = JSLRS_NULL_MARK; |
649 |
lrs->rootCount = 0; |
650 |
lrs->topChunk = &lrs->firstChunk; |
651 |
lrs->firstChunk.down = NULL; |
652 |
cx->localRootStack = lrs; |
653 |
} |
654 |
|
655 |
/* Push lrs->scopeMark to save it for restore when leaving. */ |
656 |
mark = js_PushLocalRoot(cx, lrs, INT_TO_JSVAL(lrs->scopeMark)); |
657 |
if (mark < 0) |
658 |
return JS_FALSE; |
659 |
lrs->scopeMark = (uint32) mark; |
660 |
return JS_TRUE; |
661 |
} |
662 |
|
663 |
void |
664 |
js_LeaveLocalRootScopeWithResult(JSContext *cx, jsval rval) |
665 |
{ |
666 |
JSLocalRootStack *lrs; |
667 |
uint32 mark, m, n; |
668 |
JSLocalRootChunk *lrc; |
669 |
|
670 |
/* Defend against buggy native callers. */ |
671 |
lrs = cx->localRootStack; |
672 |
JS_ASSERT(lrs && lrs->rootCount != 0); |
673 |
if (!lrs || lrs->rootCount == 0) |
674 |
return; |
675 |
|
676 |
mark = lrs->scopeMark; |
677 |
JS_ASSERT(mark != JSLRS_NULL_MARK); |
678 |
if (mark == JSLRS_NULL_MARK) |
679 |
return; |
680 |
|
681 |
/* Free any chunks being popped by this leave operation. */ |
682 |
m = mark >> JSLRS_CHUNK_SHIFT; |
683 |
n = (lrs->rootCount - 1) >> JSLRS_CHUNK_SHIFT; |
684 |
while (n > m) { |
685 |
lrc = lrs->topChunk; |
686 |
JS_ASSERT(lrc != &lrs->firstChunk); |
687 |
lrs->topChunk = lrc->down; |
688 |
JS_free(cx, lrc); |
689 |
--n; |
690 |
} |
691 |
|
692 |
/* |
693 |
* Pop the scope, restoring lrs->scopeMark. If rval is a GC-thing, push |
694 |
* it on the caller's scope, or store it in lastInternalResult if we are |
695 |
* leaving the outermost scope. We don't need to allocate a new lrc |
696 |
* because we can overwrite the old mark's slot with rval. |
697 |
*/ |
698 |
lrc = lrs->topChunk; |
699 |
m = mark & JSLRS_CHUNK_MASK; |
700 |
lrs->scopeMark = (uint32) JSVAL_TO_INT(lrc->roots[m]); |
701 |
if (JSVAL_IS_GCTHING(rval) && !JSVAL_IS_NULL(rval)) { |
702 |
if (mark == 0) { |
703 |
cx->weakRoots.lastInternalResult = rval; |
704 |
} else { |
705 |
/* |
706 |
* Increment m to avoid the "else if (m == 0)" case below. If |
707 |
* rval is not a GC-thing, that case would take care of freeing |
708 |
* any chunk that contained only the old mark. Since rval *is* |
709 |
* a GC-thing here, we want to reuse that old mark's slot. |
710 |
*/ |
711 |
lrc->roots[m++] = rval; |
712 |
++mark; |
713 |
} |
714 |
} |
715 |
lrs->rootCount = (uint32) mark; |
716 |
|
717 |
/* |
718 |
* Free the stack eagerly, risking malloc churn. The alternative would |
719 |
* require an lrs->entryCount member, maintained by Enter and Leave, and |
720 |
* tested by the GC in addition to the cx->localRootStack non-null test. |
721 |
* |
722 |
* That approach would risk hoarding 264 bytes (net) per context. Right |
723 |
* now it seems better to give fresh (dirty in CPU write-back cache, and |
724 |
* the data is no longer needed) memory back to the malloc heap. |
725 |
*/ |
726 |
if (mark == 0) { |
727 |
cx->localRootStack = NULL; |
728 |
JS_free(cx, lrs); |
729 |
} else if (m == 0) { |
730 |
lrs->topChunk = lrc->down; |
731 |
JS_free(cx, lrc); |
732 |
} |
733 |
} |
734 |
|
735 |
void |
736 |
js_ForgetLocalRoot(JSContext *cx, jsval v) |
737 |
{ |
738 |
JSLocalRootStack *lrs; |
739 |
uint32 i, j, m, n, mark; |
740 |
JSLocalRootChunk *lrc, *lrc2; |
741 |
jsval top; |
742 |
|
743 |
lrs = cx->localRootStack; |
744 |
JS_ASSERT(lrs && lrs->rootCount); |
745 |
if (!lrs || lrs->rootCount == 0) |
746 |
return; |
747 |
|
748 |
/* Prepare to pop the top-most value from the stack. */ |
749 |
n = lrs->rootCount - 1; |
750 |
m = n & JSLRS_CHUNK_MASK; |
751 |
lrc = lrs->topChunk; |
752 |
top = lrc->roots[m]; |
753 |
|
754 |
/* Be paranoid about calls on an empty scope. */ |
755 |
mark = lrs->scopeMark; |
756 |
JS_ASSERT(mark < n); |
757 |
if (mark >= n) |
758 |
return; |
759 |
|
760 |
/* If v was not the last root pushed in the top scope, find it. */ |
761 |
if (top != v) { |
762 |
/* Search downward in case v was recently pushed. */ |
763 |
i = n; |
764 |
j = m; |
765 |
lrc2 = lrc; |
766 |
while (--i > mark) { |
767 |
if (j == 0) |
768 |
lrc2 = lrc2->down; |
769 |
j = i & JSLRS_CHUNK_MASK; |
770 |
if (lrc2->roots[j] == v) |
771 |
break; |
772 |
} |
773 |
|
774 |
/* If we didn't find v in this scope, assert and bail out. */ |
775 |
JS_ASSERT(i != mark); |
776 |
if (i == mark) |
777 |
return; |
778 |
|
779 |
/* Swap top and v so common tail code can pop v. */ |
780 |
lrc2->roots[j] = top; |
781 |
} |
782 |
|
783 |
/* Pop the last value from the stack. */ |
784 |
lrc->roots[m] = JSVAL_NULL; |
785 |
lrs->rootCount = n; |
786 |
if (m == 0) { |
787 |
JS_ASSERT(n != 0); |
788 |
JS_ASSERT(lrc != &lrs->firstChunk); |
789 |
lrs->topChunk = lrc->down; |
790 |
JS_free(cx, lrc); |
791 |
} |
792 |
} |
793 |
|
794 |
int |
795 |
js_PushLocalRoot(JSContext *cx, JSLocalRootStack *lrs, jsval v) |
796 |
{ |
797 |
uint32 n, m; |
798 |
JSLocalRootChunk *lrc; |
799 |
|
800 |
n = lrs->rootCount; |
801 |
m = n & JSLRS_CHUNK_MASK; |
802 |
if (n == 0 || m != 0) { |
803 |
/* |
804 |
* At start of first chunk, or not at start of a non-first top chunk. |
805 |
* Check for lrs->rootCount overflow. |
806 |
*/ |
807 |
if ((uint32)(n + 1) == 0) { |
808 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
809 |
JSMSG_TOO_MANY_LOCAL_ROOTS); |
810 |
return -1; |
811 |
} |
812 |
lrc = lrs->topChunk; |
813 |
JS_ASSERT(n != 0 || lrc == &lrs->firstChunk); |
814 |
} else { |
815 |
/* |
816 |
* After lrs->firstChunk, trying to index at a power-of-two chunk |
817 |
* boundary: need a new chunk. |
818 |
*/ |
819 |
lrc = (JSLocalRootChunk *) JS_malloc(cx, sizeof *lrc); |
820 |
if (!lrc) |
821 |
return -1; |
822 |
lrc->down = lrs->topChunk; |
823 |
lrs->topChunk = lrc; |
824 |
} |
825 |
lrs->rootCount = n + 1; |
826 |
lrc->roots[m] = v; |
827 |
return (int) n; |
828 |
} |
829 |
|
830 |
void |
831 |
js_TraceLocalRoots(JSTracer *trc, JSLocalRootStack *lrs) |
832 |
{ |
833 |
uint32 n, m, mark; |
834 |
JSLocalRootChunk *lrc; |
835 |
jsval v; |
836 |
|
837 |
n = lrs->rootCount; |
838 |
if (n == 0) |
839 |
return; |
840 |
|
841 |
mark = lrs->scopeMark; |
842 |
lrc = lrs->topChunk; |
843 |
do { |
844 |
while (--n > mark) { |
845 |
m = n & JSLRS_CHUNK_MASK; |
846 |
v = lrc->roots[m]; |
847 |
JS_ASSERT(JSVAL_IS_GCTHING(v) && v != JSVAL_NULL); |
848 |
JS_SET_TRACING_INDEX(trc, "local_root", n); |
849 |
js_CallValueTracerIfGCThing(trc, v); |
850 |
if (m == 0) |
851 |
lrc = lrc->down; |
852 |
} |
853 |
m = n & JSLRS_CHUNK_MASK; |
854 |
mark = JSVAL_TO_INT(lrc->roots[m]); |
855 |
if (m == 0) |
856 |
lrc = lrc->down; |
857 |
} while (n != 0); |
858 |
JS_ASSERT(!lrc); |
859 |
} |
860 |
|
861 |
static void |
862 |
ReportError(JSContext *cx, const char *message, JSErrorReport *reportp) |
863 |
{ |
864 |
/* |
865 |
* Check the error report, and set a JavaScript-catchable exception |
866 |
* if the error is defined to have an associated exception. If an |
867 |
* exception is thrown, then the JSREPORT_EXCEPTION flag will be set |
868 |
* on the error report, and exception-aware hosts should ignore it. |
869 |
*/ |
870 |
JS_ASSERT(reportp); |
871 |
if (reportp->errorNumber == JSMSG_UNCAUGHT_EXCEPTION) |
872 |
reportp->flags |= JSREPORT_EXCEPTION; |
873 |
|
874 |
/* |
875 |
* Call the error reporter only if an exception wasn't raised. |
876 |
* |
877 |
* If an exception was raised, then we call the debugErrorHook |
878 |
* (if present) to give it a chance to see the error before it |
879 |
* propagates out of scope. This is needed for compatability |
880 |
* with the old scheme. |
881 |
*/ |
882 |
if (!cx->fp || !js_ErrorToException(cx, message, reportp)) { |
883 |
js_ReportErrorAgain(cx, message, reportp); |
884 |
} else if (cx->debugHooks->debugErrorHook && cx->errorReporter) { |
885 |
JSDebugErrorHook hook = cx->debugHooks->debugErrorHook; |
886 |
/* test local in case debugErrorHook changed on another thread */ |
887 |
if (hook) |
888 |
hook(cx, message, reportp, cx->debugHooks->debugErrorHookData); |
889 |
} |
890 |
} |
891 |
|
892 |
/* |
893 |
* We don't post an exception in this case, since doing so runs into |
894 |
* complications of pre-allocating an exception object which required |
895 |
* running the Exception class initializer early etc. |
896 |
* Instead we just invoke the errorReporter with an "Out Of Memory" |
897 |
* type message, and then hope the process ends swiftly. |
898 |
*/ |
899 |
void |
900 |
js_ReportOutOfMemory(JSContext *cx) |
901 |
{ |
902 |
JSStackFrame *fp; |
903 |
JSErrorReport report; |
904 |
JSErrorReporter onError = cx->errorReporter; |
905 |
|
906 |
/* Get the message for this error, but we won't expand any arguments. */ |
907 |
const JSErrorFormatString *efs = |
908 |
js_GetLocalizedErrorMessage(cx, NULL, NULL, JSMSG_OUT_OF_MEMORY); |
909 |
const char *msg = efs ? efs->format : "Out of memory"; |
910 |
|
911 |
/* Fill out the report, but don't do anything that requires allocation. */ |
912 |
memset(&report, 0, sizeof (struct JSErrorReport)); |
913 |
report.flags = JSREPORT_ERROR; |
914 |
report.errorNumber = JSMSG_OUT_OF_MEMORY; |
915 |
|
916 |
/* |
917 |
* Walk stack until we find a frame that is associated with some script |
918 |
* rather than a native frame. |
919 |
*/ |
920 |
for (fp = cx->fp; fp; fp = fp->down) { |
921 |
if (fp->regs) { |
922 |
report.filename = fp->script->filename; |
923 |
report.lineno = js_PCToLineNumber(cx, fp->script, fp->regs->pc); |
924 |
break; |
925 |
} |
926 |
} |
927 |
|
928 |
/* |
929 |
* If debugErrorHook is present then we give it a chance to veto sending |
930 |
* the error on to the regular ErrorReporter. We also clear a pending |
931 |
* exception if any now so the hooks can replace the out-of-memory error |
932 |
* by a script-catchable exception. |
933 |
*/ |
934 |
cx->throwing = JS_FALSE; |
935 |
if (onError) { |
936 |
JSDebugErrorHook hook = cx->debugHooks->debugErrorHook; |
937 |
if (hook && |
938 |
!hook(cx, msg, &report, cx->debugHooks->debugErrorHookData)) { |
939 |
onError = NULL; |
940 |
} |
941 |
} |
942 |
|
943 |
if (onError) |
944 |
onError(cx, msg, &report); |
945 |
} |
946 |
|
947 |
void |
948 |
js_ReportOutOfScriptQuota(JSContext *cx) |
949 |
{ |
950 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
951 |
JSMSG_SCRIPT_STACK_QUOTA); |
952 |
} |
953 |
|
954 |
void |
955 |
js_ReportOverRecursed(JSContext *cx) |
956 |
{ |
957 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED); |
958 |
} |
959 |
|
960 |
void |
961 |
js_ReportAllocationOverflow(JSContext *cx) |
962 |
{ |
963 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_ALLOC_OVERFLOW); |
964 |
} |
965 |
|
966 |
JSBool |
967 |
js_ReportErrorVA(JSContext *cx, uintN flags, const char *format, va_list ap) |
968 |
{ |
969 |
char *message; |
970 |
jschar *ucmessage; |
971 |
size_t messagelen; |
972 |
JSStackFrame *fp; |
973 |
JSErrorReport report; |
974 |
JSBool warning; |
975 |
|
976 |
if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) |
977 |
return JS_TRUE; |
978 |
|
979 |
message = JS_vsmprintf(format, ap); |
980 |
if (!message) |
981 |
return JS_FALSE; |
982 |
messagelen = strlen(message); |
983 |
|
984 |
memset(&report, 0, sizeof (struct JSErrorReport)); |
985 |
report.flags = flags; |
986 |
report.errorNumber = JSMSG_USER_DEFINED_ERROR; |
987 |
report.ucmessage = ucmessage = js_InflateString(cx, message, &messagelen); |
988 |
|
989 |
/* Find the top-most active script frame, for best line number blame. */ |
990 |
for (fp = cx->fp; fp; fp = fp->down) { |
991 |
if (fp->regs) { |
992 |
report.filename = fp->script->filename; |
993 |
report.lineno = js_PCToLineNumber(cx, fp->script, fp->regs->pc); |
994 |
break; |
995 |
} |
996 |
} |
997 |
|
998 |
warning = JSREPORT_IS_WARNING(report.flags); |
999 |
if (warning && JS_HAS_WERROR_OPTION(cx)) { |
1000 |
report.flags &= ~JSREPORT_WARNING; |
1001 |
warning = JS_FALSE; |
1002 |
} |
1003 |
|
1004 |
ReportError(cx, message, &report); |
1005 |
free(message); |
1006 |
JS_free(cx, ucmessage); |
1007 |
return warning; |
1008 |
} |
1009 |
|
1010 |
/* |
1011 |
* The arguments from ap need to be packaged up into an array and stored |
1012 |
* into the report struct. |
1013 |
* |
1014 |
* The format string addressed by the error number may contain operands |
1015 |
* identified by the format {N}, where N is a decimal digit. Each of these |
1016 |
* is to be replaced by the Nth argument from the va_list. The complete |
1017 |
* message is placed into reportp->ucmessage converted to a JSString. |
1018 |
* |
1019 |
* Returns true if the expansion succeeds (can fail if out of memory). |
1020 |
*/ |
1021 |
JSBool |
1022 |
js_ExpandErrorArguments(JSContext *cx, JSErrorCallback callback, |
1023 |
void *userRef, const uintN errorNumber, |
1024 |
char **messagep, JSErrorReport *reportp, |
1025 |
JSBool *warningp, JSBool charArgs, va_list ap) |
1026 |
{ |
1027 |
const JSErrorFormatString *efs; |
1028 |
int i; |
1029 |
int argCount; |
1030 |
|
1031 |
*warningp = JSREPORT_IS_WARNING(reportp->flags); |
1032 |
if (*warningp && JS_HAS_WERROR_OPTION(cx)) { |
1033 |
reportp->flags &= ~JSREPORT_WARNING; |
1034 |
*warningp = JS_FALSE; |
1035 |
} |
1036 |
|
1037 |
*messagep = NULL; |
1038 |
|
1039 |
/* Most calls supply js_GetErrorMessage; if this is so, assume NULL. */ |
1040 |
if (!callback || callback == js_GetErrorMessage) |
1041 |
efs = js_GetLocalizedErrorMessage(cx, userRef, NULL, errorNumber); |
1042 |
else |
1043 |
efs = callback(userRef, NULL, errorNumber); |
1044 |
if (efs) { |
1045 |
size_t totalArgsLength = 0; |
1046 |
size_t argLengths[10]; /* only {0} thru {9} supported */ |
1047 |
argCount = efs->argCount; |
1048 |
JS_ASSERT(argCount <= 10); |
1049 |
if (argCount > 0) { |
1050 |
/* |
1051 |
* Gather the arguments into an array, and accumulate |
1052 |
* their sizes. We allocate 1 more than necessary and |
1053 |
* null it out to act as the caboose when we free the |
1054 |
* pointers later. |
1055 |
*/ |
1056 |
reportp->messageArgs = (const jschar **) |
1057 |
JS_malloc(cx, sizeof(jschar *) * (argCount + 1)); |
1058 |
if (!reportp->messageArgs) |
1059 |
return JS_FALSE; |
1060 |
reportp->messageArgs[argCount] = NULL; |
1061 |
for (i = 0; i < argCount; i++) { |
1062 |
if (charArgs) { |
1063 |
char *charArg = va_arg(ap, char *); |
1064 |
size_t charArgLength = strlen(charArg); |
1065 |
reportp->messageArgs[i] |
1066 |
= js_InflateString(cx, charArg, &charArgLength); |
1067 |
if (!reportp->messageArgs[i]) |
1068 |
goto error; |
1069 |
} else { |
1070 |
reportp->messageArgs[i] = va_arg(ap, jschar *); |
1071 |
} |
1072 |
argLengths[i] = js_strlen(reportp->messageArgs[i]); |
1073 |
totalArgsLength += argLengths[i]; |
1074 |
} |
1075 |
/* NULL-terminate for easy copying. */ |
1076 |
reportp->messageArgs[i] = NULL; |
1077 |
} |
1078 |
/* |
1079 |
* Parse the error format, substituting the argument X |
1080 |
* for {X} in the format. |
1081 |
*/ |
1082 |
if (argCount > 0) { |
1083 |
if (efs->format) { |
1084 |
jschar *buffer, *fmt, *out; |
1085 |
int expandedArgs = 0; |
1086 |
size_t expandedLength; |
1087 |
size_t len = strlen(efs->format); |
1088 |
|
1089 |
buffer = fmt = js_InflateString (cx, efs->format, &len); |
1090 |
if (!buffer) |
1091 |
goto error; |
1092 |
expandedLength = len |
1093 |
- (3 * argCount) /* exclude the {n} */ |
1094 |
+ totalArgsLength; |
1095 |
|
1096 |
/* |
1097 |
* Note - the above calculation assumes that each argument |
1098 |
* is used once and only once in the expansion !!! |
1099 |
*/ |
1100 |
reportp->ucmessage = out = (jschar *) |
1101 |
JS_malloc(cx, (expandedLength + 1) * sizeof(jschar)); |
1102 |
if (!out) { |
1103 |
JS_free (cx, buffer); |
1104 |
goto error; |
1105 |
} |
1106 |
while (*fmt) { |
1107 |
if (*fmt == '{') { |
1108 |
if (isdigit(fmt[1])) { |
1109 |
int d = JS7_UNDEC(fmt[1]); |
1110 |
JS_ASSERT(d < argCount); |
1111 |
js_strncpy(out, reportp->messageArgs[d], |
1112 |
argLengths[d]); |
1113 |
out += argLengths[d]; |
1114 |
fmt += 3; |
1115 |
expandedArgs++; |
1116 |
continue; |
1117 |
} |
1118 |
} |
1119 |
*out++ = *fmt++; |
1120 |
} |
1121 |
JS_ASSERT(expandedArgs == argCount); |
1122 |
*out = 0; |
1123 |
JS_free (cx, buffer); |
1124 |
*messagep = |
1125 |
js_DeflateString(cx, reportp->ucmessage, |
1126 |
(size_t)(out - reportp->ucmessage)); |
1127 |
if (!*messagep) |
1128 |
goto error; |
1129 |
} |
1130 |
} else { |
1131 |
/* |
1132 |
* Zero arguments: the format string (if it exists) is the |
1133 |
* entire message. |
1134 |
*/ |
1135 |
if (efs->format) { |
1136 |
size_t len; |
1137 |
*messagep = JS_strdup(cx, efs->format); |
1138 |
if (!*messagep) |
1139 |
goto error; |
1140 |
len = strlen(*messagep); |
1141 |
reportp->ucmessage = js_InflateString(cx, *messagep, &len); |
1142 |
if (!reportp->ucmessage) |
1143 |
goto error; |
1144 |
} |
1145 |
} |
1146 |
} |
1147 |
if (*messagep == NULL) { |
1148 |
/* where's the right place for this ??? */ |
1149 |
const char *defaultErrorMessage |
1150 |
= "No error message available for error number %d"; |
1151 |
size_t nbytes = strlen(defaultErrorMessage) + 16; |
1152 |
*messagep = (char *)JS_malloc(cx, nbytes); |
1153 |
if (!*messagep) |
1154 |
goto error; |
1155 |
JS_snprintf(*messagep, nbytes, defaultErrorMessage, errorNumber); |
1156 |
} |
1157 |
return JS_TRUE; |
1158 |
|
1159 |
error: |
1160 |
if (reportp->messageArgs) { |
1161 |
/* free the arguments only if we allocated them */ |
1162 |
if (charArgs) { |
1163 |
i = 0; |
1164 |
while (reportp->messageArgs[i]) |
1165 |
JS_free(cx, (void *)reportp->messageArgs[i++]); |
1166 |
} |
1167 |
JS_free(cx, (void *)reportp->messageArgs); |
1168 |
reportp->messageArgs = NULL; |
1169 |
} |
1170 |
if (reportp->ucmessage) { |
1171 |
JS_free(cx, (void *)reportp->ucmessage); |
1172 |
reportp->ucmessage = NULL; |
1173 |
} |
1174 |
if (*messagep) { |
1175 |
JS_free(cx, (void *)*messagep); |
1176 |
*messagep = NULL; |
1177 |
} |
1178 |
return JS_FALSE; |
1179 |
} |
1180 |
|
1181 |
JSBool |
1182 |
js_ReportErrorNumberVA(JSContext *cx, uintN flags, JSErrorCallback callback, |
1183 |
void *userRef, const uintN errorNumber, |
1184 |
JSBool charArgs, va_list ap) |
1185 |
{ |
1186 |
JSStackFrame *fp; |
1187 |
JSErrorReport report; |
1188 |
char *message; |
1189 |
JSBool warning; |
1190 |
|
1191 |
if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) |
1192 |
return JS_TRUE; |
1193 |
|
1194 |
memset(&report, 0, sizeof (struct JSErrorReport)); |
1195 |
report.flags = flags; |
1196 |
report.errorNumber = errorNumber; |
1197 |
|
1198 |
/* |
1199 |
* If we can't find out where the error was based on the current frame, |
1200 |
* see if the next frame has a script/pc combo we can use. |
1201 |
*/ |
1202 |
for (fp = cx->fp; fp; fp = fp->down) { |
1203 |
if (fp->regs) { |
1204 |
report.filename = fp->script->filename; |
1205 |
report.lineno = js_PCToLineNumber(cx, fp->script, fp->regs->pc); |
1206 |
break; |
1207 |
} |
1208 |
} |
1209 |
|
1210 |
if (!js_ExpandErrorArguments(cx, callback, userRef, errorNumber, |
1211 |
&message, &report, &warning, charArgs, ap)) { |
1212 |
return JS_FALSE; |
1213 |
} |
1214 |
|
1215 |
ReportError(cx, message, &report); |
1216 |
|
1217 |
if (message) |
1218 |
JS_free(cx, message); |
1219 |
if (report.messageArgs) { |
1220 |
/* |
1221 |
* js_ExpandErrorArguments owns its messageArgs only if it had to |
1222 |
* inflate the arguments (from regular |char *|s). |
1223 |
*/ |
1224 |
if (charArgs) { |
1225 |
int i = 0; |
1226 |
while (report.messageArgs[i]) |
1227 |
JS_free(cx, (void *)report.messageArgs[i++]); |
1228 |
} |
1229 |
JS_free(cx, (void *)report.messageArgs); |
1230 |
} |
1231 |
if (report.ucmessage) |
1232 |
JS_free(cx, (void *)report.ucmessage); |
1233 |
|
1234 |
return warning; |
1235 |
} |
1236 |
|
1237 |
JS_FRIEND_API(void) |
1238 |
js_ReportErrorAgain(JSContext *cx, const char *message, JSErrorReport *reportp) |
1239 |
{ |
1240 |
JSErrorReporter onError; |
1241 |
|
1242 |
if (!message) |
1243 |
return; |
1244 |
|
1245 |
if (cx->lastMessage) |
1246 |
free(cx->lastMessage); |
1247 |
cx->lastMessage = JS_strdup(cx, message); |
1248 |
if (!cx->lastMessage) |
1249 |
return; |
1250 |
onError = cx->errorReporter; |
1251 |
|
1252 |
/* |
1253 |
* If debugErrorHook is present then we give it a chance to veto |
1254 |
* sending the error on to the regular ErrorReporter. |
1255 |
*/ |
1256 |
if (onError) { |
1257 |
JSDebugErrorHook hook = cx->debugHooks->debugErrorHook; |
1258 |
if (hook && |
1259 |
!hook(cx, cx->lastMessage, reportp, |
1260 |
cx->debugHooks->debugErrorHookData)) { |
1261 |
onError = NULL; |
1262 |
} |
1263 |
} |
1264 |
if (onError) |
1265 |
onError(cx, cx->lastMessage, reportp); |
1266 |
} |
1267 |
|
1268 |
void |
1269 |
js_ReportIsNotDefined(JSContext *cx, const char *name) |
1270 |
{ |
1271 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_DEFINED, name); |
1272 |
} |
1273 |
|
1274 |
JSBool |
1275 |
js_ReportIsNullOrUndefined(JSContext *cx, intN spindex, jsval v, |
1276 |
JSString *fallback) |
1277 |
{ |
1278 |
char *bytes; |
1279 |
JSBool ok; |
1280 |
|
1281 |
bytes = js_DecompileValueGenerator(cx, spindex, v, fallback); |
1282 |
if (!bytes) |
1283 |
return JS_FALSE; |
1284 |
|
1285 |
if (strcmp(bytes, js_undefined_str) == 0 || |
1286 |
strcmp(bytes, js_null_str) == 0) { |
1287 |
ok = JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, |
1288 |
js_GetErrorMessage, NULL, |
1289 |
JSMSG_NO_PROPERTIES, bytes, |
1290 |
NULL, NULL); |
1291 |
} else if (JSVAL_IS_VOID(v)) { |
1292 |
ok = JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, |
1293 |
js_GetErrorMessage, NULL, |
1294 |
JSMSG_NULL_OR_UNDEFINED, bytes, |
1295 |
js_undefined_str, NULL); |
1296 |
} else { |
1297 |
JS_ASSERT(JSVAL_IS_NULL(v)); |
1298 |
ok = JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, |
1299 |
js_GetErrorMessage, NULL, |
1300 |
JSMSG_NULL_OR_UNDEFINED, bytes, |
1301 |
js_null_str, NULL); |
1302 |
} |
1303 |
|
1304 |
JS_free(cx, bytes); |
1305 |
return ok; |
1306 |
} |
1307 |
|
1308 |
void |
1309 |
js_ReportMissingArg(JSContext *cx, jsval *vp, uintN arg) |
1310 |
{ |
1311 |
char argbuf[11]; |
1312 |
char *bytes; |
1313 |
JSAtom *atom; |
1314 |
|
1315 |
JS_snprintf(argbuf, sizeof argbuf, "%u", arg); |
1316 |
bytes = NULL; |
1317 |
if (VALUE_IS_FUNCTION(cx, *vp)) { |
1318 |
atom = GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(*vp))->atom; |
1319 |
bytes = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, *vp, |
1320 |
ATOM_TO_STRING(atom)); |
1321 |
if (!bytes) |
1322 |
return; |
1323 |
} |
1324 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
1325 |
JSMSG_MISSING_FUN_ARG, argbuf, |
1326 |
bytes ? bytes : ""); |
1327 |
JS_free(cx, bytes); |
1328 |
} |
1329 |
|
1330 |
JSBool |
1331 |
js_ReportValueErrorFlags(JSContext *cx, uintN flags, const uintN errorNumber, |
1332 |
intN spindex, jsval v, JSString *fallback, |
1333 |
const char *arg1, const char *arg2) |
1334 |
{ |
1335 |
char *bytes; |
1336 |
JSBool ok; |
1337 |
|
1338 |
JS_ASSERT(js_ErrorFormatString[errorNumber].argCount >= 1); |
1339 |
JS_ASSERT(js_ErrorFormatString[errorNumber].argCount <= 3); |
1340 |
bytes = js_DecompileValueGenerator(cx, spindex, v, fallback); |
1341 |
if (!bytes) |
1342 |
return JS_FALSE; |
1343 |
|
1344 |
ok = JS_ReportErrorFlagsAndNumber(cx, flags, js_GetErrorMessage, |
1345 |
NULL, errorNumber, bytes, arg1, arg2); |
1346 |
JS_free(cx, bytes); |
1347 |
return ok; |
1348 |
} |
1349 |
|
1350 |
#if defined DEBUG && defined XP_UNIX |
1351 |
/* For gdb usage. */ |
1352 |
void js_traceon(JSContext *cx) { cx->tracefp = stderr; } |
1353 |
void js_traceoff(JSContext *cx) { cx->tracefp = NULL; } |
1354 |
#endif |
1355 |
|
1356 |
JSErrorFormatString js_ErrorFormatString[JSErr_Limit] = { |
1357 |
#define MSG_DEF(name, number, count, exception, format) \ |
1358 |
{ format, count, exception } , |
1359 |
#include "js.msg" |
1360 |
#undef MSG_DEF |
1361 |
}; |
1362 |
|
1363 |
JS_FRIEND_API(const JSErrorFormatString *) |
1364 |
js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber) |
1365 |
{ |
1366 |
if ((errorNumber > 0) && (errorNumber < JSErr_Limit)) |
1367 |
return &js_ErrorFormatString[errorNumber]; |
1368 |
return NULL; |
1369 |
} |
1370 |
|
1371 |
JSBool |
1372 |
js_ResetOperationCount(JSContext *cx) |
1373 |
{ |
1374 |
JSScript *script; |
1375 |
|
1376 |
JS_ASSERT(cx->operationCount <= 0); |
1377 |
JS_ASSERT(cx->operationLimit > 0); |
1378 |
|
1379 |
cx->operationCount = (int32) cx->operationLimit; |
1380 |
if (cx->operationCallbackIsSet) |
1381 |
return cx->operationCallback(cx); |
1382 |
|
1383 |
if (cx->operationCallback) { |
1384 |
/* |
1385 |
* Invoke the deprecated branch callback. It may be called only when |
1386 |
* the top-most frame is scripted or JSOPTION_NATIVE_BRANCH_CALLBACK |
1387 |
* is set. |
1388 |
*/ |
1389 |
script = cx->fp ? cx->fp->script : NULL; |
1390 |
if (script || JS_HAS_OPTION(cx, JSOPTION_NATIVE_BRANCH_CALLBACK)) |
1391 |
return ((JSBranchCallback) cx->operationCallback)(cx, script); |
1392 |
} |
1393 |
return JS_TRUE; |
1394 |
} |