| 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- | |
| 2 | * | |
| 3 | * ***** BEGIN LICENSE BLOCK ***** | |
| 4 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 | |
| 5 | * | |
| 6 | * The contents of this file are subject to the Mozilla Public License Version | |
| 7 | * 1.1 (the "License"); you may not use this file except in compliance with | |
| 8 | * the License. You may obtain a copy of the License at | |
| 9 | * http://www.mozilla.org/MPL/ | |
| 10 | * | |
| 11 | * Software distributed under the License is distributed on an "AS IS" basis, | |
| 12 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License | |
| 13 | * for the specific language governing rights and limitations under the | |
| 14 | * License. | |
| 15 | * | |
| 16 | * The Original Code is Mozilla Communicator client code, released | |
| 17 | * March 31, 1998. | |
| 18 | * | |
| 19 | * The Initial Developer of the Original Code is | |
| 20 | * Netscape Communications Corporation. | |
| 21 | * Portions created by the Initial Developer are Copyright (C) 1998 | |
| 22 | * the Initial Developer. All Rights Reserved. | |
| 23 | * | |
| 24 | * Contributor(s): | |
| 25 | * | |
| 26 | * Alternatively, the contents of this file may be used under the terms of | |
| 27 | * either of the GNU General Public License Version 2 or later (the "GPL"), | |
| 28 | * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), | |
| 29 | * in which case the provisions of the GPL or the LGPL are applicable instead | |
| 30 | * of those above. If you wish to allow use of your version of this file only | |
| 31 | * under the terms of either the GPL or the LGPL, and not to allow others to | |
| 32 | * use your version of this file under the terms of the MPL, indicate your | |
| 33 | * decision by deleting the provisions above and replace them with the notice | |
| 34 | * and other provisions required by the GPL or the LGPL. If you do not delete | |
| 35 | * the provisions above, a recipient may use your version of this file under | |
| 36 | * the terms of any one of the MPL, the GPL or the LGPL. | |
| 37 | * | |
| 38 | * ***** END LICENSE BLOCK ***** */ | |
| 39 | ||
| 40 | /* | |
| 41 | * JS execution context. | |
| 42 | */ | |
| 43 | #include "jsstddef.h" | |
| 44 | #include <stdarg.h> | |
| 45 | #include <stdlib.h> | |
| 46 | #include <string.h> | |
| 47 | #include "jstypes.h" | |
| 48 | #include "jsarena.h" /* Added by JSIFY */ | |
| 49 | #include "jsutil.h" /* Added by JSIFY */ | |
| 50 | #include "jsclist.h" | |
| 51 | #include "jsprf.h" | |
| 52 | #include "jsatom.h" | |
| 53 | #include "jscntxt.h" | |
| 54 | #include "jsconfig.h" | |
| 55 | #include "jsdbgapi.h" | |
| 56 | #include "jsexn.h" | |
| 57 | #include "jsgc.h" | |
| 58 | #include "jslock.h" | |
| 59 | #include "jsnum.h" | |
| 60 | #include "jsobj.h" | |
| 61 | #include "jsopcode.h" | |
| 62 | #include "jsscan.h" | |
| 63 | #include "jsscript.h" | |
| 64 | #include "jsstr.h" | |
| 65 | ||
| 66 | JSContext * | |
| 67 | js_NewContext(JSRuntime *rt, size_t stackChunkSize) | |
| 68 | 0 | { |
| 69 | 0 | JSContext *cx; |
| 70 | 0 | JSBool ok, first; |
| 71 | ||
| 72 | 0 | cx = (JSContext *) malloc(sizeof *cx); |
| 73 | 0 | if (!cx) |
| 74 | 0 | return NULL; |
| 75 | 0 | memset(cx, 0, sizeof *cx); |
| 76 | ||
| 77 | 0 | cx->runtime = rt; |
| 78 | #if JS_STACK_GROWTH_DIRECTION > 0 | |
| 79 | cx->stackLimit = (jsuword)-1; | |
| 80 | #endif | |
| 81 | #ifdef JS_THREADSAFE | |
| 82 | js_InitContextForLocking(cx); | |
| 83 | #endif | |
| 84 | ||
| 85 | 0 | JS_LOCK_GC(rt); |
| 86 | 0 | for (;;) { |
| 87 | 0 | first = (rt->contextList.next == &rt->contextList); |
| 88 | 0 | if (rt->state == JSRTS_UP) { |
| 89 | 0 | JS_ASSERT(!first); |
| 90 | 0 | break; |
| 91 | } | |
| 92 | 0 | if (rt->state == JSRTS_DOWN) { |
| 93 | 0 | JS_ASSERT(first); |
| 94 | 0 | rt->state = JSRTS_LAUNCHING; |
| 95 | break; | |
| 96 | } | |
| 97 | 0 | JS_WAIT_CONDVAR(rt->stateChange, JS_NO_TIMEOUT); |
| 98 | } | |
| 99 | 0 | JS_APPEND_LINK(&cx->links, &rt->contextList); |
| 100 | 0 | JS_UNLOCK_GC(rt); |
| 101 | ||
| 102 | /* | |
| 103 | * First we do the infallible, every-time per-context initializations. | |
| 104 | * Should a later, fallible initialization (js_InitRegExpStatics, e.g., | |
| 105 | * or the stuff under 'if (first)' below) fail, at least the version | |
| 106 | * and arena-pools will be valid and safe to use (say, from the last GC | |
| 107 | * done by js_DestroyContext). | |
| 108 | */ | |
| 109 | 0 | cx->version = JSVERSION_DEFAULT; |
| 110 | 0 | cx->jsop_eq = JSOP_EQ; |
| 111 | 0 | cx->jsop_ne = JSOP_NE; |
| 112 | 0 | JS_InitArenaPool(&cx->stackPool, "stack", stackChunkSize, sizeof(jsval)); |
| 113 | 0 | JS_InitArenaPool(&cx->tempPool, "temp", 1024, sizeof(jsdouble)); |
| 114 | ||
| 115 | #if JS_HAS_REGEXPS | |
| 116 | 0 | if (!js_InitRegExpStatics(cx, &cx->regExpStatics)) { |
| 117 | 0 | js_DestroyContext(cx, JS_NO_GC); |
| 118 | 0 | return NULL; |
| 119 | } | |
| 120 | #endif | |
| 121 | #if JS_HAS_EXCEPTIONS | |
| 122 | 0 | cx->throwing = JS_FALSE; |
| 123 | #endif | |
| 124 | ||
| 125 | /* | |
| 126 | * If cx is the first context on this runtime, initialize well-known atoms, | |
| 127 | * keywords, numbers, and strings. If one of these steps should fail, the | |
| 128 | * runtime will be left in a partially initialized state, with zeroes and | |
| 129 | * nulls stored in the default-initialized remainder of the struct. We'll | |
| 130 | * clean the runtime up under js_DestroyContext, because cx will be "last" | |
| 131 | * as well as "first". | |
| 132 | */ | |
| 133 | 0 | if (first) { |
| 134 | 0 | ok = (rt->atomState.liveAtoms == 0) |
| 135 | ? js_InitAtomState(cx, &rt->atomState) | |
| 136 | : js_InitPinnedAtoms(cx, &rt->atomState); | |
| 137 | 0 | if (ok) |
| 138 | 0 | ok = js_InitScanner(cx); |
| 139 | 0 | if (ok) |
| 140 | 0 | ok = js_InitRuntimeNumberState(cx); |
| 141 | 0 | if (ok) |
| 142 | 0 | ok = js_InitRuntimeScriptState(cx); |
| 143 | 0 | if (ok) |
| 144 | 0 | ok = js_InitRuntimeStringState(cx); |
| 145 | 0 | if (!ok) { |
| 146 | 0 | js_DestroyContext(cx, JS_NO_GC); |
| 147 | 0 | return NULL; |
| 148 | } | |
| 149 | ||
| 150 | 0 | JS_LOCK_GC(rt); |
| 151 | 0 | rt->state = JSRTS_UP; |
| 152 | 0 | JS_NOTIFY_ALL_CONDVAR(rt->stateChange); |
| 153 | 0 | JS_UNLOCK_GC(rt); |
| 154 | } | |
| 155 | ||
| 156 | 0 | return cx; |
| 157 | } | |
| 158 | ||
| 159 | void | |
| 160 | js_DestroyContext(JSContext *cx, JSGCMode gcmode) | |
| 161 | 0 | { |
| 162 | 0 | JSRuntime *rt; |
| 163 | 0 | JSBool last; |
| 164 | 0 | JSArgumentFormatMap *map; |
| 165 | ||
| 166 | 0 | rt = cx->runtime; |
| 167 | ||
| 168 | /* Remove cx from context list first. */ | |
| 169 | 0 | JS_LOCK_GC(rt); |
| 170 | 0 | JS_ASSERT(rt->state == JSRTS_UP || rt->state == JSRTS_LAUNCHING); |
| 171 | 0 | JS_REMOVE_LINK(&cx->links); |
| 172 | 0 | last = (rt->contextList.next == &rt->contextList); |
| 173 | 0 | if (last) |
| 174 | 0 | rt->state = JSRTS_LANDING; |
| 175 | 0 | JS_UNLOCK_GC(rt); |
| 176 | ||
| 177 | 0 | if (last) { |
| 178 | #ifdef JS_THREADSAFE | |
| 179 | /* | |
| 180 | * If cx is not in a request already, begin one now so that we wait | |
| 181 | * for any racing GC started on a not-last context to finish, before | |
| 182 | * we plow ahead and unpin atoms. Note that even though we begin a | |
| 183 | * request here if necessary, we end all requests on cx below before | |
| 184 | * forcing a final GC. This lets any not-last context destruction | |
| 185 | * racing in another thread try to force or maybe run the GC, but by | |
| 186 | * that point, rt->state will not be JSRTS_UP, and that GC attempt | |
| 187 | * will return early. | |
| 188 | */ | |
| 189 | if (cx->requestDepth == 0) | |
| 190 | JS_BeginRequest(cx); | |
| 191 | #endif | |
| 192 | ||
| 193 | /* Unpin all pinned atoms before final GC. */ | |
| 194 | 0 | js_UnpinPinnedAtoms(&rt->atomState); |
| 195 | ||
| 196 | /* Unlock and clear GC things held by runtime pointers. */ | |
| 197 | 0 | js_FinishRuntimeNumberState(cx); |
| 198 | 0 | js_FinishRuntimeStringState(cx); |
| 199 | ||
| 200 | /* Clear debugging state to remove GC roots. */ | |
| 201 | 0 | JS_ClearAllTraps(cx); |
| 202 | 0 | JS_ClearAllWatchPoints(cx); |
| 203 | } | |
| 204 | ||
| 205 | #if JS_HAS_REGEXPS | |
| 206 | /* | |
| 207 | * Remove more GC roots in regExpStatics, then collect garbage. | |
| 208 | * XXX anti-modularity alert: we rely on the call to js_RemoveRoot within | |
| 209 | * XXX this function call to wait for any racing GC to complete, in the | |
| 210 | * XXX case where JS_DestroyContext is called outside of a request on cx | |
| 211 | */ | |
| 212 | 0 | js_FreeRegExpStatics(cx, &cx->regExpStatics); |
| 213 | #endif | |
| 214 | ||
| 215 | #ifdef JS_THREADSAFE | |
| 216 | /* | |
| 217 | * Destroying a context implicitly calls JS_EndRequest(). Also, we must | |
| 218 | * end our request here in case we are "last" -- in that event, another | |
| 219 | * js_DestroyContext that was not last might be waiting in the GC for our | |
| 220 | * request to end. We'll let it run below, just before we do the truly | |
| 221 | * final GC and then free atom state. | |
| 222 | * | |
| 223 | * At this point, cx must be inaccessible to other threads. It's off the | |
| 224 | * rt->contextList, and it should not be reachable via any object private | |
| 225 | * data structure. | |
| 226 | */ | |
| 227 | while (cx->requestDepth != 0) | |
| 228 | JS_EndRequest(cx); | |
| 229 | #endif | |
| 230 | ||
| 231 | 0 | if (last) { |
| 232 | /* Always force, so we wait for any racing GC to finish. */ | |
| 233 | 0 | js_ForceGC(cx, GC_LAST_CONTEXT); |
| 234 | ||
| 235 | /* Iterate until no finalizer removes a GC root or lock. */ | |
| 236 | 0 | while (rt->gcPoke) |
| 237 | 0 | js_GC(cx, GC_LAST_CONTEXT); |
| 238 | ||
| 239 | /* Try to free atom state, now that no unrooted scripts survive. */ | |
| 240 | 0 | if (rt->atomState.liveAtoms == 0) |
| 241 | 0 | js_FreeAtomState(cx, &rt->atomState); |
| 242 | ||
| 243 | /* Now after the last GC can we free the script filename table. */ | |
| 244 | 0 | js_FinishRuntimeScriptState(cx); |
| 245 | ||
| 246 | /* Take the runtime down, now that it has no contexts or atoms. */ | |
| 247 | 0 | JS_LOCK_GC(rt); |
| 248 | 0 | rt->state = JSRTS_DOWN; |
| 249 | 0 | JS_NOTIFY_ALL_CONDVAR(rt->stateChange); |
| 250 | 0 | JS_UNLOCK_GC(rt); |
| 251 | } else { | |
| 252 | 0 | if (gcmode == JS_FORCE_GC) |
| 253 | 0 | js_ForceGC(cx, 0); |
| 254 | 0 | else if (gcmode == JS_MAYBE_GC) |
| 255 | 0 | JS_MaybeGC(cx); |
| 256 | } | |
| 257 | ||
| 258 | /* Free the stuff hanging off of cx. */ | |
| 259 | 0 | JS_FinishArenaPool(&cx->stackPool); |
| 260 | 0 | JS_FinishArenaPool(&cx->tempPool); |
| 261 | 0 | if (cx->lastMessage) |
| 262 | 0 | free(cx->lastMessage); |
| 263 | ||
| 264 | /* Remove any argument formatters. */ | |
| 265 | 0 | map = cx->argumentFormatMap; |
| 266 | 0 | while (map) { |
| 267 | 0 | JSArgumentFormatMap *temp = map; |
| 268 | 0 | map = map->next; |
| 269 | 0 | JS_free(cx, temp); |
| 270 | } | |
| 271 | ||
| 272 | /* Destroy the resolve recursion damper. */ | |
| 273 | 0 | if (cx->resolvingTable) { |
| 274 | 0 | JS_DHashTableDestroy(cx->resolvingTable); |
| 275 | 0 | cx->resolvingTable = NULL; |
| 276 | } | |
| 277 | ||
| 278 | /* Finally, free cx itself. */ | |
| 279 | 0 | free(cx); |
| 280 | } | |
| 281 | ||
| 282 | JSBool | |
| 283 | js_ValidContextPointer(JSRuntime *rt, JSContext *cx) | |
| 284 | 0 | { |
| 285 | 0 | JSCList *cl; |
| 286 | ||
| 287 | 0 | for (cl = rt->contextList.next; cl != &rt->contextList; cl = cl->next) { |
| 288 | 0 | if (cl == &cx->links) |
| 289 | 0 | return JS_TRUE; |
| 290 | } | |
| 291 | JS_RUNTIME_METER(rt, deadContexts); | |
| 292 | 0 | return JS_FALSE; |
| 293 | } | |
| 294 | ||
| 295 | JSContext * | |
| 296 | js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp) | |
| 297 | 0 | { |
| 298 | 0 | JSContext *cx = *iterp; |
| 299 | ||
| 300 | 0 | if (unlocked) |
| 301 | 0 | JS_LOCK_GC(rt); |
| 302 | 0 | if (!cx) |
| 303 | 0 | cx = (JSContext *)&rt->contextList; |
| 304 | 0 | cx = (JSContext *)cx->links.next; |
| 305 | 0 | if (&cx->links == &rt->contextList) |
| 306 | 0 | cx = NULL; |
| 307 | 0 | *iterp = cx; |
| 308 | 0 | if (unlocked) |
| 309 | 0 | JS_UNLOCK_GC(rt); |
| 310 | 0 | return cx; |
| 311 | } | |
| 312 | ||
| 313 | static void | |
| 314 | ReportError(JSContext *cx, const char *message, JSErrorReport *reportp) | |
| 315 | 0 | { |
| 316 | /* | |
| 317 | * Check the error report, and set a JavaScript-catchable exception | |
| 318 | * if the error is defined to have an associated exception. If an | |
| 319 | * exception is thrown, then the JSREPORT_EXCEPTION flag will be set | |
| 320 | * on the error report, and exception-aware hosts should ignore it. | |
| 321 | */ | |
| 322 | 0 | if (reportp && reportp->errorNumber == JSMSG_UNCAUGHT_EXCEPTION) |
| 323 | 0 | reportp->flags |= JSREPORT_EXCEPTION; |
| 324 | ||
| 325 | #if JS_HAS_ERROR_EXCEPTIONS | |
| 326 | /* | |
| 327 | * Call the error reporter only if an exception wasn't raised. | |
| 328 | * | |
| 329 | * If an exception was raised, then we call the debugErrorHook | |
| 330 | * (if present) to give it a chance to see the error before it | |
| 331 | * propagates out of scope. This is needed for compatability | |
| 332 | * with the old scheme. | |
| 333 | */ | |
| 334 | 0 | if (!js_ErrorToException(cx, message, reportp)) { |
| 335 | 0 | js_ReportErrorAgain(cx, message, reportp); |
| 336 | 0 | } else if (cx->runtime->debugErrorHook && cx->errorReporter) { |
| 337 | 0 | JSDebugErrorHook hook = cx->runtime->debugErrorHook; |
| 338 | /* test local in case debugErrorHook changed on another thread */ | |
| 339 | 0 | if (hook) |
| 340 | 0 | hook(cx, message, reportp, cx->runtime->debugErrorHookData); |
| 341 | } | |
| 342 | #else | |
| 343 | js_ReportErrorAgain(cx, message, reportp); | |
| 344 | #endif | |
| 345 | } | |
| 346 | ||
| 347 | /* | |
| 348 | * We don't post an exception in this case, since doing so runs into | |
| 349 | * complications of pre-allocating an exception object which required | |
| 350 | * running the Exception class initializer early etc. | |
| 351 | * Instead we just invoke the errorReporter with an "Out Of Memory" | |
| 352 | * type message, and then hope the process ends swiftly. | |
| 353 | */ | |
| 354 | void | |
| 355 | js_ReportOutOfMemory(JSContext *cx, JSErrorCallback callback) | |
| 356 | 0 | { |
| 357 | 0 | JSStackFrame *fp; |
| 358 | 0 | JSErrorReport report; |
| 359 | 0 | JSErrorReporter onError = cx->errorReporter; |
| 360 | ||
| 361 | /* Get the message for this error, but we won't expand any arguments. */ | |
| 362 | 0 | const JSErrorFormatString *efs = callback(NULL, NULL, JSMSG_OUT_OF_MEMORY); |
| 363 | 0 | const char *msg = efs ? efs->format : "Out of memory"; |
| 364 | ||
| 365 | /* Fill out the report, but don't do anything that requires allocation. */ | |
| 366 | 0 | memset(&report, 0, sizeof (struct JSErrorReport)); |
| 367 | 0 | report.flags = JSREPORT_ERROR; |
| 368 | 0 | report.errorNumber = JSMSG_OUT_OF_MEMORY; |
| 369 | ||
| 370 | /* | |
| 371 | * Walk stack until we find a frame that is associated with some script | |
| 372 | * rather than a native frame. | |
| 373 | */ | |
| 374 | 0 | for (fp = cx->fp; fp; fp = fp->down) { |
| 375 | 0 | if (fp->script && fp->pc) { |
| 376 | 0 | report.filename = fp->script->filename; |
| 377 | 0 | report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc); |
| 378 | 0 | break; |
| 379 | } | |
| 380 | } | |
| 381 | ||
| 382 | /* | |
| 383 | * If debugErrorHook is present then we give it a chance to veto | |
| 384 | * sending the error on to the regular ErrorReporter. | |
| 385 | */ | |
| 386 | 0 | if (onError) { |
| 387 | 0 | JSDebugErrorHook hook = cx->runtime->debugErrorHook; |
| 388 | 0 | if (hook && |
| 389 | !hook(cx, msg, &report, cx->runtime->debugErrorHookData)) { | |
| 390 | 0 | onError = NULL; |
| 391 | } | |
| 392 | } | |
| 393 | ||
| 394 | 0 | if (onError) |
| 395 | 0 | onError(cx, msg, &report); |
| 396 | } | |
| 397 | ||
| 398 | JSBool | |
| 399 | js_ReportErrorVA(JSContext *cx, uintN flags, const char *format, va_list ap) | |
| 400 | 0 | { |
| 401 | 0 | char *last; |
| 402 | 0 | JSStackFrame *fp; |
| 403 | 0 | JSErrorReport report; |
| 404 | 0 | JSBool warning; |
| 405 | ||
| 406 | 0 | if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) |
| 407 | 0 | return JS_TRUE; |
| 408 | ||
| 409 | 0 | last = JS_vsmprintf(format, ap); |
| 410 | 0 | if (!last) |
| 411 | 0 | return JS_FALSE; |
| 412 | ||
| 413 | 0 | memset(&report, 0, sizeof (struct JSErrorReport)); |
| 414 | 0 | report.flags = flags; |
| 415 | ||
| 416 | /* Find the top-most active script frame, for best line number blame. */ | |
| 417 | 0 | for (fp = cx->fp; fp; fp = fp->down) { |
| 418 | 0 | if (fp->script && fp->pc) { |
| 419 | 0 | report.filename = fp->script->filename; |
| 420 | 0 | report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc); |
| 421 | 0 | break; |
| 422 | } | |
| 423 | } | |
| 424 | ||
| 425 | 0 | warning = JSREPORT_IS_WARNING(report.flags); |
| 426 | 0 | if (warning && JS_HAS_WERROR_OPTION(cx)) { |
| 427 | 0 | report.flags &= ~JSREPORT_WARNING; |
| 428 | 0 | warning = JS_FALSE; |
| 429 | } | |
| 430 | ||
| 431 | 0 | ReportError(cx, last, &report); |
| 432 | 0 | free(last); |
| 433 | 0 | return warning; |
| 434 | } | |
| 435 | ||
| 436 | /* | |
| 437 | * The arguments from ap need to be packaged up into an array and stored | |
| 438 | * into the report struct. | |
| 439 | * | |
| 440 | * The format string addressed by the error number may contain operands | |
| 441 | * identified by the format {N}, where N is a decimal digit. Each of these | |
| 442 | * is to be replaced by the Nth argument from the va_list. The complete | |
| 443 | * message is placed into reportp->ucmessage converted to a JSString. | |
| 444 | * | |
| 445 | * Returns true if the expansion succeeds (can fail if out of memory). | |
| 446 | */ | |
| 447 | JSBool | |
| 448 | js_ExpandErrorArguments(JSContext *cx, JSErrorCallback callback, | |
| 449 | void *userRef, const uintN errorNumber, | |
| 450 | char **messagep, JSErrorReport *reportp, | |
| 451 | JSBool *warningp, JSBool charArgs, va_list ap) | |
| 452 | 0 | { |
| 453 | 0 | const JSErrorFormatString *efs; |
| 454 | 0 | int i; |
| 455 | 0 | int argCount; |
| 456 | ||
| 457 | 0 | *warningp = JSREPORT_IS_WARNING(reportp->flags); |
| 458 | 0 | if (*warningp && JS_HAS_WERROR_OPTION(cx)) { |
| 459 | 0 | reportp->flags &= ~JSREPORT_WARNING; |
| 460 | 0 | *warningp = JS_FALSE; |
| 461 | } | |
| 462 | ||
| 463 | 0 | *messagep = NULL; |
| 464 | 0 | if (callback) { |
| 465 | 0 | efs = callback(userRef, NULL, errorNumber); |
| 466 | 0 | if (efs) { |
| 467 | 0 | size_t totalArgsLength = 0; |
| 468 | 0 | size_t argLengths[10]; /* only {0} thru {9} supported */ |
| 469 | 0 | argCount = efs->argCount; |
| 470 | 0 | JS_ASSERT(argCount <= 10); |
| 471 | 0 | if (argCount > 0) { |
| 472 | /* | |
| 473 | * Gather the arguments into an array, and accumulate | |
| 474 | * their sizes. We allocate 1 more than necessary and | |
| 475 | * null it out to act as the caboose when we free the | |
| 476 | * pointers later. | |
| 477 | */ | |
| 478 | 0 | reportp->messageArgs = (const jschar **) |
| 479 | JS_malloc(cx, sizeof(jschar *) * (argCount + 1)); | |
| 480 | 0 | if (!reportp->messageArgs) |
| 481 | 0 | return JS_FALSE; |
| 482 | 0 | reportp->messageArgs[argCount] = NULL; |
| 483 | 0 | for (i = 0; i < argCount; i++) { |
| 484 | 0 | if (charArgs) { |
| 485 | 0 | char *charArg = va_arg(ap, char *); |
| 486 | 0 | reportp->messageArgs[i] |
| 487 | = js_InflateString(cx, charArg, strlen(charArg)); | |
| 488 | 0 | if (!reportp->messageArgs[i]) |
| 489 | 0 | goto error; |
| 490 | } | |
| 491 | else | |
| 492 | 0 | reportp->messageArgs[i] = va_arg(ap, jschar *); |
| 493 | 0 | argLengths[i] = js_strlen(reportp->messageArgs[i]); |
| 494 | 0 | totalArgsLength += argLengths[i]; |
| 495 | } | |
| 496 | /* NULL-terminate for easy copying. */ | |
| 497 | 0 | reportp->messageArgs[i] = NULL; |
| 498 | } | |
| 499 | /* | |
| 500 | * Parse the error format, substituting the argument X | |
| 501 | * for {X} in the format. | |
| 502 | */ | |
| 503 | 0 | if (argCount > 0) { |
| 504 | 0 | if (efs->format) { |
| 505 | 0 | const char *fmt; |
| 506 | 0 | const jschar *arg; |
| 507 | 0 | jschar *out; |
| 508 | 0 | int expandedArgs = 0; |
| 509 | size_t expandedLength | |
| 510 | 0 | = strlen(efs->format) |
| 511 | - (3 * argCount) /* exclude the {n} */ | |
| 512 | 0 | + totalArgsLength; |
| 513 | /* | |
| 514 | * Note - the above calculation assumes that each argument | |
| 515 | * is used once and only once in the expansion !!! | |
| 516 | */ | |
| 517 | 0 | reportp->ucmessage = out = (jschar *) |
| 518 | JS_malloc(cx, (expandedLength + 1) * sizeof(jschar)); | |
| 519 | 0 | if (!out) |
| 520 | 0 | goto error; |
| 521 | 0 | fmt = efs->format; |
| 522 | 0 | while (*fmt) { |
| 523 | 0 | if (*fmt == '{') { |
| 524 | 0 | if (isdigit(fmt[1])) { |
| 525 | 0 | int d = JS7_UNDEC(fmt[1]); |
| 526 | 0 | JS_ASSERT(expandedArgs < argCount); |
| 527 | 0 | arg = reportp->messageArgs[d]; |
| 528 | 0 | js_strncpy(out, arg, argLengths[d]); |
| 529 | 0 | out += argLengths[d]; |
| 530 | 0 | fmt += 3; |
| 531 | 0 | expandedArgs++; |
| 532 | 0 | continue; |
| 533 | } | |
| 534 | } | |
| 535 | /* | |
| 536 | * is this kosher? | |
| 537 | */ | |
| 538 | 0 | *out++ = (unsigned char)(*fmt++); |
| 539 | } | |
| 540 | 0 | JS_ASSERT(expandedArgs == argCount); |
| 541 | 0 | *out = 0; |
| 542 | 0 | *messagep = |
| 543 | js_DeflateString(cx, reportp->ucmessage, | |
| 544 | (size_t)(out - reportp->ucmessage)); | |
| 545 | 0 | if (!*messagep) |
| 546 | 0 | goto error; |
| 547 | } | |
| 548 | } else { | |
| 549 | /* | |
| 550 | * Zero arguments: the format string (if it exists) is the | |
| 551 | * entire message. | |
| 552 | */ | |
| 553 | 0 | if (efs->format) { |
| 554 | 0 | *messagep = JS_strdup(cx, efs->format); |
| 555 | 0 | if (!*messagep) |
| 556 | 0 | goto error; |
| 557 | 0 | reportp->ucmessage |
| 558 | = js_InflateString(cx, *messagep, strlen(*messagep)); | |
| 559 | 0 | if (!reportp->ucmessage) |
| 560 | 0 | goto error; |
| 561 | } | |
| 562 | } | |
| 563 | } | |
| 564 | } | |
| 565 | 0 | if (*messagep == NULL) { |
| 566 | /* where's the right place for this ??? */ | |
| 567 | const char *defaultErrorMessage | |
| 568 | 0 | = "No error message available for error number %d"; |
| 569 | 0 | size_t nbytes = strlen(defaultErrorMessage) + 16; |
| 570 | 0 | *messagep = (char *)JS_malloc(cx, nbytes); |
| 571 | 0 | if (!*messagep) |
| 572 | 0 | goto error; |
| 573 | 0 | JS_snprintf(*messagep, nbytes, defaultErrorMessage, errorNumber); |
| 574 | } | |
| 575 | 0 | return JS_TRUE; |
| 576 | ||
| 577 | error: | |
| 578 | 0 | if (reportp->messageArgs) { |
| 579 | 0 | i = 0; |
| 580 | 0 | while (reportp->messageArgs[i]) |
| 581 | 0 | JS_free(cx, (void *)reportp->messageArgs[i++]); |
| 582 | 0 | JS_free(cx, (void *)reportp->messageArgs); |
| 583 | 0 | reportp->messageArgs = NULL; |
| 584 | } | |
| 585 | 0 | if (reportp->ucmessage) { |
| 586 | 0 | JS_free(cx, (void *)reportp->ucmessage); |
| 587 | 0 | reportp->ucmessage = NULL; |
| 588 | } | |
| 589 | 0 | if (*messagep) { |
| 590 | 0 | JS_free(cx, (void *)*messagep); |
| 591 | 0 | *messagep = NULL; |
| 592 | } | |
| 593 | 0 | return JS_FALSE; |
| 594 | } | |
| 595 | ||
| 596 | JSBool | |
| 597 | js_ReportErrorNumberVA(JSContext *cx, uintN flags, JSErrorCallback callback, | |
| 598 | void *userRef, const uintN errorNumber, | |
| 599 | JSBool charArgs, va_list ap) | |
| 600 | 0 | { |
| 601 | 0 | JSStackFrame *fp; |
| 602 | 0 | JSErrorReport report; |
| 603 | 0 | char *message; |
| 604 | 0 | JSBool warning; |
| 605 | ||
| 606 | 0 | if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) |
| 607 | 0 | return JS_TRUE; |
| 608 | ||
| 609 | 0 | memset(&report, 0, sizeof (struct JSErrorReport)); |
| 610 | 0 | report.flags = flags; |
| 611 | 0 | report.errorNumber = errorNumber; |
| 612 | ||
| 613 | /* | |
| 614 | * If we can't find out where the error was based on the current frame, | |
| 615 | * see if the next frame has a script/pc combo we can use. | |
| 616 | */ | |
| 617 | 0 | for (fp = cx->fp; fp; fp = fp->down) { |
| 618 | 0 | if (fp->script && fp->pc) { |
| 619 | 0 | report.filename = fp->script->filename; |
| 620 | 0 | report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc); |
| 621 | 0 | break; |
| 622 | } | |
| 623 | } | |
| 624 | ||
| 625 | 0 | if (!js_ExpandErrorArguments(cx, callback, userRef, errorNumber, |
| 626 | &message, &report, &warning, charArgs, ap)) { | |
| 627 | 0 | return JS_FALSE; |
| 628 | } | |
| 629 | ||
| 630 | 0 | ReportError(cx, message, &report); |
| 631 | ||
| 632 | 0 | if (message) |
| 633 | 0 | JS_free(cx, message); |
| 634 | 0 | if (report.messageArgs) { |
| 635 | 0 | int i = 0; |
| 636 | 0 | while (report.messageArgs[i]) |
| 637 | 0 | JS_free(cx, (void *)report.messageArgs[i++]); |
| 638 | 0 | JS_free(cx, (void *)report.messageArgs); |
| 639 | } | |
| 640 | 0 | if (report.ucmessage) |
| 641 | 0 | JS_free(cx, (void *)report.ucmessage); |
| 642 | ||
| 643 | 0 | return warning; |
| 644 | } | |
| 645 | ||
| 646 | JS_FRIEND_API(void) | |
| 647 | js_ReportErrorAgain(JSContext *cx, const char *message, JSErrorReport *reportp) | |
| 648 | 0 | { |
| 649 | 0 | JSErrorReporter onError; |
| 650 | ||
| 651 | 0 | if (!message) |
| 652 | 0 | return; |
| 653 | ||
| 654 | 0 | if (cx->lastMessage) |
| 655 | 0 | free(cx->lastMessage); |
| 656 | 0 | cx->lastMessage = JS_strdup(cx, message); |
| 657 | 0 | if (!cx->lastMessage) |
| 658 | 0 | return; |
| 659 | 0 | onError = cx->errorReporter; |
| 660 | ||
| 661 | /* | |
| 662 | * If debugErrorHook is present then we give it a chance to veto | |
| 663 | * sending the error on to the regular ErrorReporter. | |
| 664 | */ | |
| 665 | 0 | if (onError) { |
| 666 | 0 | JSDebugErrorHook hook = cx->runtime->debugErrorHook; |
| 667 | 0 | if (hook && |
| 668 | !hook(cx, cx->lastMessage, reportp, | |
| 669 | cx->runtime->debugErrorHookData)) { | |
| 670 | 0 | onError = NULL; |
| 671 | } | |
| 672 | } | |
| 673 | 0 | if (onError) |
| 674 | 0 | onError(cx, cx->lastMessage, reportp); |
| 675 | } | |
| 676 | ||
| 677 | void | |
| 678 | js_ReportIsNotDefined(JSContext *cx, const char *name) | |
| 679 | 0 | { |
| 680 | 0 | JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_DEFINED, name); |
| 681 | } | |
| 682 | ||
| 683 | #if defined DEBUG && defined XP_UNIX | |
| 684 | /* For gdb usage. */ | |
| 685 | void js_traceon(JSContext *cx) { cx->tracefp = stderr; } | |
| 686 | void js_traceoff(JSContext *cx) { cx->tracefp = NULL; } | |
| 687 | #endif | |
| 688 | ||
| 689 | JSErrorFormatString js_ErrorFormatString[JSErr_Limit] = { | |
| 690 | #if JS_HAS_DFLT_MSG_STRINGS | |
| 691 | #define MSG_DEF(name, number, count, exception, format) \ | |
| 692 | { format, count } , | |
| 693 | #else | |
| 694 | #define MSG_DEF(name, number, count, exception, format) \ | |
| 695 | { NULL, count } , | |
| 696 | #endif | |
| 697 | #include "js.msg" | |
| 698 | #undef MSG_DEF | |
| 699 | }; | |
| 700 | ||
| 701 | const JSErrorFormatString * | |
| 702 | js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber) | |
| 703 | 0 | { |
| 704 | 0 | if ((errorNumber > 0) && (errorNumber < JSErr_Limit)) |
| 705 | 0 | return &js_ErrorFormatString[errorNumber]; |
| 706 | 0 | return NULL; |