/* * Like JSEnumerateOp, but enum provides contextual information as follows: * * JSENUMERATE_INIT: allocate private enum struct in state_p, return number * of elements in *id_p * JSENUMERATE_NEXT: return next property id in *id_p, and if no new property * free state_p and set to JSVAL_NULL * JSENUMERATE_DESTROY : destroy state_p * * Note that in a for ... in loop, this will be called first on the object, * then on its prototype. * */ static JSBool importer_new_enumerate(JSContext *context, JSObject **object, JSIterateOp enum_op, jsval *state_p, jsid *id_p) { ImporterIterator *iter; switch (enum_op) { case JSENUMERATE_INIT_ALL: case JSENUMERATE_INIT: { Importer *priv; JSObject *search_path; jsval search_path_val; guint32 search_path_len; guint32 i; jsid search_path_name; if (state_p) *state_p = JSVAL_NULL; if (id_p) *id_p = INT_TO_JSID(0); priv = priv_from_js(context, *object); if (!priv) /* we are enumerating the prototype properties */ return JS_TRUE; search_path_name = gjs_runtime_get_const_string(JS_GetRuntime(context), GJS_STRING_SEARCH_PATH); if (!gjs_object_require_property(context, *object, "importer", search_path_name, &search_path_val)) return JS_FALSE; if (!JSVAL_IS_OBJECT(search_path_val)) { gjs_throw(context, "searchPath property on importer is not an object"); return JS_FALSE; } search_path = JSVAL_TO_OBJECT(search_path_val); if (!JS_IsArrayObject(context, search_path)) { gjs_throw(context, "searchPath property on importer is not an array"); return JS_FALSE; } if (!JS_GetArrayLength(context, search_path, &search_path_len)) { gjs_throw(context, "searchPath array has no length"); return JS_FALSE; } iter = importer_iterator_new(); for (i = 0; i < search_path_len; ++i) { char *dirname = NULL; char *init_path; const char *filename; jsval elem; GDir *dir = NULL; elem = JSVAL_VOID; if (!JS_GetElement(context, search_path, i, &elem)) { /* this means there was an exception, while elem == JSVAL_VOID * means no element found */ importer_iterator_free(iter); return JS_FALSE; } if (JSVAL_IS_VOID(elem)) continue; if (!JSVAL_IS_STRING(elem)) { gjs_throw(context, "importer searchPath contains non-string"); importer_iterator_free(iter); return JS_FALSE; } if (!gjs_string_to_utf8(context, elem, &dirname)) { importer_iterator_free(iter); return JS_FALSE; /* Error message already set */ } init_path = g_build_filename(dirname, MODULE_INIT_FILENAME, NULL); load_module_elements(context, *object, iter, init_path); g_free(init_path); dir = g_dir_open(dirname, 0, NULL); if (!dir) { g_free(dirname); continue; } while ((filename = g_dir_read_name(dir))) { char *full_path; /* skip hidden files and directories (.svn, .git, ...) */ if (filename[0] == '.') continue; /* skip module init file */ if (strcmp(filename, MODULE_INIT_FILENAME) == 0) continue; full_path = g_build_filename(dirname, filename, NULL); if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) { g_ptr_array_add(iter->elements, g_strdup(filename)); } else { if (g_str_has_suffix(filename, "."G_MODULE_SUFFIX) || g_str_has_suffix(filename, ".js")) { g_ptr_array_add(iter->elements, g_strndup(filename, strlen(filename) - 3)); } } g_free(full_path); } g_dir_close(dir); g_free(dirname); } if (state_p) *state_p = PRIVATE_TO_JSVAL(iter); if (id_p) *id_p = INT_TO_JSID(iter->elements->len); break; } case JSENUMERATE_NEXT: { jsval element_val; if (!state_p) { gjs_throw(context, "Enumerate with no iterator set?"); return JS_FALSE; } if (JSVAL_IS_NULL(*state_p)) /* Iterating prototype */ return JS_TRUE; iter = JSVAL_TO_PRIVATE(*state_p); if (iter->index < iter->elements->len) { if (!gjs_string_from_utf8(context, g_ptr_array_index(iter->elements, iter->index++), -1, &element_val)) return JS_FALSE; if (!JS_ValueToId(context, element_val, id_p)) return JS_FALSE; break; } /* else fall through to destroying the iterator */ } case JSENUMERATE_DESTROY: { if (state_p && !JSVAL_IS_NULL(*state_p)) { iter = JSVAL_TO_PRIVATE(*state_p); importer_iterator_free(iter); *state_p = JSVAL_NULL; } } } return JS_TRUE; }
/* * Like JSEnumerateOp, but enum provides contextual information as follows: * * JSENUMERATE_INIT: allocate private enum struct in state_p, return number * of elements in *id_p * JSENUMERATE_NEXT: return next property id in *id_p, and if no new property * free state_p and set to JSVAL_NULL * JSENUMERATE_DESTROY : destroy state_p * * Note that in a for ... in loop, this will be called first on the object, * then on its prototype. * */ static JSBool importer_new_enumerate(JSContext *context, JS::HandleObject object, JSIterateOp enum_op, JS::MutableHandleValue statep, JS::MutableHandleId idp) { ImporterIterator *iter; switch (enum_op) { case JSENUMERATE_INIT_ALL: case JSENUMERATE_INIT: { Importer *priv; JSObject *search_path; jsval search_path_val; uint32_t search_path_len; uint32_t i; jsid search_path_name; statep.set(JSVAL_NULL); idp.set(INT_TO_JSID(0)); priv = priv_from_js(context, object); if (!priv) /* we are enumerating the prototype properties */ return JS_TRUE; search_path_name = gjs_context_get_const_string(context, GJS_STRING_SEARCH_PATH); if (!gjs_object_require_property(context, object, "importer", search_path_name, &search_path_val)) return JS_FALSE; if (!search_path_val.isObject()) { gjs_throw(context, "searchPath property on importer is not an object"); return JS_FALSE; } search_path = JSVAL_TO_OBJECT(search_path_val); if (!JS_IsArrayObject(context, search_path)) { gjs_throw(context, "searchPath property on importer is not an array"); return JS_FALSE; } if (!JS_GetArrayLength(context, search_path, &search_path_len)) { gjs_throw(context, "searchPath array has no length"); return JS_FALSE; } iter = importer_iterator_new(); for (i = 0; i < search_path_len; ++i) { std::string dirname; std::string init_path; const char *filename; jsval elem; std::vector<std::string> dir; elem = JSVAL_VOID; if (!JS_GetElement(context, search_path, i, &elem)) { /* this means there was an exception, while elem == JSVAL_VOID * means no element found */ importer_iterator_free(iter); return JS_FALSE; } if (JSVAL_IS_VOID(elem)) continue; if (!JSVAL_IS_STRING(elem)) { gjs_throw(context, "importer searchPath contains non-string"); importer_iterator_free(iter); return JS_FALSE; } if (!gjs_string_to_utf8(context, elem, dirname)) { importer_iterator_free(iter); return JS_FALSE; /* Error message already set */ } init_path = pathCombine(dirname, MODULE_INIT_FILENAME); load_module_elements(context, object, iter, init_path); dir = enumerateFilesInDirectory(dirname); if (dir.size() == 0) { continue; } for(auto filename : dir) { std::string full_path; /* skip hidden files and directories (.svn, .git, ...) */ if (filename[0] == '.') continue; /* skip module init file */ if (filename == MODULE_INIT_FILENAME) continue; full_path = pathCombine(dirname, filename); if (is_directory(full_path)) { iter->elements.push_back(filename); } else { if (filename.rfind(MODULE_SUFFIX) != std::string::npos || filename.rfind(JS_SUFFIX) != std::string::npos) { iter->elements.push_back(filename.substr(0, filename.size()-3)); } } } } statep.set(PRIVATE_TO_JSVAL(iter)); idp.set(INT_TO_JSID(iter->elements.size())); break; } case JSENUMERATE_NEXT: { jsval element_val; if (JSVAL_IS_NULL(statep)) /* Iterating prototype */ return JS_TRUE; iter = (ImporterIterator*) JSVAL_TO_PRIVATE(statep); if (iter->index < iter->elements.size()) { if (!gjs_string_from_utf8(context, iter->elements.at(iter->index++), &element_val)) return JS_FALSE; jsid id; if (!JS_ValueToId(context, element_val, &id)) return JS_FALSE; idp.set(id); break; } /* else fall through to destroying the iterator */ } case JSENUMERATE_DESTROY: { if (!JSVAL_IS_NULL(statep)) { iter = (ImporterIterator*) JSVAL_TO_PRIVATE(statep); importer_iterator_free(iter); statep.set(JSVAL_NULL); } } } return JS_TRUE; }
JSBool js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp) { JSErrNum errorNumber; const JSErrorFormatString *errorString; JSExnType exn; jsval tv[4]; JSTempValueRooter tvr; JSBool ok; JSObject *errProto, *errObject; JSString *messageStr, *filenameStr; /* * Tell our caller to report immediately if cx has no active frames, or if * this report is just a warning. */ JS_ASSERT(reportp); if (!cx->fp || JSREPORT_IS_WARNING(reportp->flags)) return JS_FALSE; /* Find the exception index associated with this error. */ errorNumber = (JSErrNum) reportp->errorNumber; errorString = js_GetLocalizedErrorMessage(cx, NULL, NULL, errorNumber); exn = errorString ? (JSExnType) errorString->exnType : JSEXN_NONE; JS_ASSERT(exn < JSEXN_LIMIT); #if defined( DEBUG_mccabe ) && defined ( PRINTNAMES ) /* Print the error name and the associated exception name to stderr */ fprintf(stderr, "%s\t%s\n", errortoexnname[errorNumber].name, errortoexnname[errorNumber].exception); #endif /* * Return false (no exception raised) if no exception is associated * with the given error number. */ if (exn == JSEXN_NONE) return JS_FALSE; /* * Prevent runaway recursion, via cx->generatingError. If an out-of-memory * error occurs, no exception object will be created, but we don't assume * that OOM is the only kind of error that subroutines of this function * called below might raise. */ if (cx->generatingError) return JS_FALSE; /* After this point the control must flow through the label out. */ cx->generatingError = JS_TRUE; /* Protect the newly-created strings below from nesting GCs. */ memset(tv, 0, sizeof tv); JS_PUSH_TEMP_ROOT(cx, JS_ARRAY_LENGTH(tv), tv, &tvr); /* * Try to get an appropriate prototype by looking up the corresponding * exception constructor name in the scope chain of the current context's * top stack frame, or in the global object if no frame is active. */ ok = js_GetClassPrototype(cx, NULL, INT_TO_JSID(exceptions[exn].key), &errProto); if (!ok) goto out; tv[0] = OBJECT_TO_JSVAL(errProto); errObject = js_NewObject(cx, &js_ErrorClass, errProto, NULL, 0); if (!errObject) { ok = JS_FALSE; goto out; } tv[1] = OBJECT_TO_JSVAL(errObject); messageStr = JS_NewStringCopyZ(cx, message); if (!messageStr) { ok = JS_FALSE; goto out; } tv[2] = STRING_TO_JSVAL(messageStr); filenameStr = JS_NewStringCopyZ(cx, reportp->filename); if (!filenameStr) { ok = JS_FALSE; goto out; } tv[3] = STRING_TO_JSVAL(filenameStr); ok = InitExnPrivate(cx, errObject, messageStr, filenameStr, reportp->lineno, reportp); if (!ok) goto out; JS_SetPendingException(cx, OBJECT_TO_JSVAL(errObject)); /* Flag the error report passed in to indicate an exception was raised. */ reportp->flags |= JSREPORT_EXCEPTION; out: JS_POP_TEMP_ROOT(cx, &tvr); cx->generatingError = JS_FALSE; return ok; }
JSObject * js_InitExceptionClasses(JSContext *cx, JSObject *obj) { JSObject *obj_proto, *protos[JSEXN_LIMIT]; int i; /* * If lazy class initialization occurs for any Error subclass, then all * classes are initialized, starting with Error. To avoid reentry and * redundant initialization, we must not pass a null proto parameter to * js_NewObject below, when called for the Error superclass. We need to * ensure that Object.prototype is the proto of Error.prototype. * * See the equivalent code to ensure that parent_proto is non-null when * JS_InitClass calls js_NewObject, in jsapi.c. */ if (!js_GetClassPrototype(cx, obj, INT_TO_JSID(JSProto_Object), &obj_proto)) { return NULL; } if (!js_EnterLocalRootScope(cx)) return NULL; /* Initialize the prototypes first. */ for (i = 0; exceptions[i].name != 0; i++) { JSAtom *atom; JSFunction *fun; JSString *nameString; int protoIndex = exceptions[i].protoIndex; /* Make the prototype for the current constructor name. */ protos[i] = js_NewObject(cx, &js_ErrorClass, (protoIndex != JSEXN_NONE) ? protos[protoIndex] : obj_proto, obj, 0); if (!protos[i]) break; /* So exn_finalize knows whether to destroy private data. */ STOBJ_SET_SLOT(protos[i], JSSLOT_PRIVATE, JSVAL_VOID); /* Make a constructor function for the current name. */ atom = cx->runtime->atomState.classAtoms[exceptions[i].key]; fun = js_DefineFunction(cx, obj, atom, exceptions[i].native, 3, 0); if (!fun) break; /* Make this constructor make objects of class Exception. */ fun->u.n.clasp = &js_ErrorClass; /* Make the prototype and constructor links. */ if (!js_SetClassPrototype(cx, FUN_OBJECT(fun), protos[i], JSPROP_READONLY | JSPROP_PERMANENT)) { break; } /* proto bootstrap bit from JS_InitClass omitted. */ nameString = JS_NewStringCopyZ(cx, exceptions[i].name); if (!nameString) break; /* Add the name property to the prototype. */ if (!JS_DefineProperty(cx, protos[i], js_name_str, STRING_TO_JSVAL(nameString), NULL, NULL, JSPROP_ENUMERATE)) { break; } /* Finally, stash the constructor for later uses. */ if (!js_SetClassObject(cx, obj, exceptions[i].key, FUN_OBJECT(fun))) break; } js_LeaveLocalRootScope(cx); if (exceptions[i].name) return NULL; /* * Add an empty message property. (To Exception.prototype only, * because this property will be the same for all the exception * protos.) */ if (!JS_DefineProperty(cx, protos[0], js_message_str, STRING_TO_JSVAL(cx->runtime->emptyString), NULL, NULL, JSPROP_ENUMERATE)) { return NULL; } if (!JS_DefineProperty(cx, protos[0], js_fileName_str, STRING_TO_JSVAL(cx->runtime->emptyString), NULL, NULL, JSPROP_ENUMERATE)) { return NULL; } if (!JS_DefineProperty(cx, protos[0], js_lineNumber_str, INT_TO_JSVAL(0), NULL, NULL, JSPROP_ENUMERATE)) { return NULL; } /* * Add methods only to Exception.prototype, because ostensibly all * exception types delegate to that. */ if (!JS_DefineFunctions(cx, protos[0], exception_methods)) return NULL; return protos[0]; }
JSObject * js_InitExceptionClasses(JSContext *cx, JSObject *obj) { jsval roots[3]; JSObject *obj_proto, *error_proto; jsval empty; /* * If lazy class initialization occurs for any Error subclass, then all * classes are initialized, starting with Error. To avoid reentry and * redundant initialization, we must not pass a null proto parameter to * js_NewObject below, when called for the Error superclass. We need to * ensure that Object.prototype is the proto of Error.prototype. * * See the equivalent code to ensure that parent_proto is non-null when * JS_InitClass calls js_NewObject, in jsapi.c. */ if (!js_GetClassPrototype(cx, obj, INT_TO_JSID(JSProto_Object), &obj_proto)) { return NULL; } memset(roots, 0, sizeof(roots)); JSAutoTempValueRooter tvr(cx, JS_ARRAY_LENGTH(roots), roots); #ifdef __GNUC__ error_proto = NULL; /* quell GCC overwarning */ #endif /* Initialize the prototypes first. */ for (intN i = JSEXN_ERR; i != JSEXN_LIMIT; i++) { JSObject *proto; JSProtoKey protoKey; JSAtom *atom; JSFunction *fun; /* Make the prototype for the current constructor name. */ proto = js_NewObject(cx, &js_ErrorClass, (i != JSEXN_ERR) ? error_proto : obj_proto, obj); if (!proto) return NULL; if (i == JSEXN_ERR) { error_proto = proto; roots[0] = OBJECT_TO_JSVAL(proto); } else { // We cannot share the root for error_proto and other prototypes // as error_proto must be rooted until the function returns. roots[1] = OBJECT_TO_JSVAL(proto); } /* So exn_finalize knows whether to destroy private data. */ proto->setPrivate(NULL); /* Make a constructor function for the current name. */ protoKey = GetExceptionProtoKey(i); atom = cx->runtime->atomState.classAtoms[protoKey]; fun = js_DefineFunction(cx, obj, atom, Exception, 3, 0); if (!fun) return NULL; roots[2] = OBJECT_TO_JSVAL(FUN_OBJECT(fun)); /* Make this constructor make objects of class Exception. */ FUN_CLASP(fun) = &js_ErrorClass; /* Make the prototype and constructor links. */ if (!js_SetClassPrototype(cx, FUN_OBJECT(fun), proto, JSPROP_READONLY | JSPROP_PERMANENT)) { return NULL; } /* Add the name property to the prototype. */ if (!JS_DefineProperty(cx, proto, js_name_str, ATOM_KEY(atom), NULL, NULL, JSPROP_ENUMERATE)) { return NULL; } /* Finally, stash the constructor for later uses. */ if (!js_SetClassObject(cx, obj, protoKey, FUN_OBJECT(fun))) return NULL; } /* * Set default values and add methods. We do it only for Error.prototype * as the rest of exceptions delegate to it. */ empty = STRING_TO_JSVAL(cx->runtime->emptyString); if (!JS_DefineProperty(cx, error_proto, js_message_str, empty, NULL, NULL, JSPROP_ENUMERATE) || !JS_DefineProperty(cx, error_proto, js_fileName_str, empty, NULL, NULL, JSPROP_ENUMERATE) || !JS_DefineProperty(cx, error_proto, js_lineNumber_str, JSVAL_ZERO, NULL, NULL, JSPROP_ENUMERATE) || !JS_DefineFunctions(cx, error_proto, exception_methods)) { return NULL; } return error_proto; }
JSBool js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer, JSONWriteCallback callback, void *data, uint32 depth) { if (depth > JSON_MAX_DEPTH) return JS_FALSE; /* encoding error */ JSBool ok = JS_TRUE; JSObject *obj = JSVAL_TO_OBJECT(*vp); JSBool isArray = JS_IsArrayObject(cx, obj); jschar output = jschar(isArray ? '[' : '{'); if (!callback(&output, 1, data)) return JS_FALSE; JSObject *iterObj = NULL; jsint i = 0; jsuint length = 0; if (isArray) { if (!js_GetLengthProperty(cx, obj, &length)) return JS_FALSE; } else { if (!js_ValueToIterator(cx, JSITER_ENUMERATE, vp)) return JS_FALSE; iterObj = JSVAL_TO_OBJECT(*vp); } jsval outputValue = JSVAL_VOID; JSAutoTempValueRooter tvr(cx, 1, &outputValue); jsval key; JSBool memberWritten = JS_FALSE; do { outputValue = JSVAL_VOID; if (isArray) { if ((jsuint)i >= length) break; ok = OBJ_GET_PROPERTY(cx, obj, INT_TO_JSID(i), &outputValue); i++; } else { ok = js_CallIteratorNext(cx, iterObj, &key); if (!ok) break; if (key == JSVAL_HOLE) break; JSString *ks; if (JSVAL_IS_STRING(key)) { ks = JSVAL_TO_STRING(key); } else { ks = js_ValueToString(cx, key); if (!ks) { ok = JS_FALSE; break; } } ok = JS_GetUCProperty(cx, obj, JS_GetStringChars(ks), JS_GetStringLength(ks), &outputValue); } if (!ok) break; // if this is an array, holes are transmitted as null if (isArray && outputValue == JSVAL_VOID) { outputValue = JSVAL_NULL; } else if (JSVAL_IS_OBJECT(outputValue)) { ok = js_TryJSON(cx, &outputValue); if (!ok) break; } // elide undefined values if (outputValue == JSVAL_VOID) continue; // output a comma unless this is the first member to write if (memberWritten) { output = jschar(','); ok = callback(&output, 1, data); if (!ok) break; } memberWritten = JS_TRUE; JSType type = JS_TypeOfValue(cx, outputValue); // Can't encode these types, so drop them if (type == JSTYPE_FUNCTION || type == JSTYPE_XML) break; // Be careful below, this string is weakly rooted. JSString *s; // If this isn't an array, we need to output a key if (!isArray) { s = js_ValueToString(cx, key); if (!s) { ok = JS_FALSE; break; } ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s)); if (!ok) break; output = jschar(':'); ok = callback(&output, 1, data); if (!ok) break; } if (!JSVAL_IS_PRIMITIVE(outputValue)) { // recurse ok = js_Stringify(cx, &outputValue, replacer, callback, data, depth + 1); } else { JSString *outputString; s = js_ValueToString(cx, outputValue); if (!s) { ok = JS_FALSE; break; } if (type == JSTYPE_STRING) { ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s)); if (!ok) break; continue; } if (type == JSTYPE_NUMBER) { if (JSVAL_IS_DOUBLE(outputValue)) { jsdouble d = *JSVAL_TO_DOUBLE(outputValue); if (!JSDOUBLE_IS_FINITE(d)) outputString = JS_NewStringCopyN(cx, "null", 4); else outputString = s; } else { outputString = s; } } else if (type == JSTYPE_BOOLEAN) { outputString = s; } else if (JSVAL_IS_NULL(outputValue)) { outputString = JS_NewStringCopyN(cx, "null", 4); } else { ok = JS_FALSE; // encoding error break; } if (!outputString) { ok = JS_FALSE; break; } ok = callback(JS_GetStringChars(outputString), JS_GetStringLength(outputString), data); } } while (ok); if (iterObj) { // Always close the iterator, but make sure not to stomp on OK ok &= js_CloseIterator(cx, *vp); // encoding error or propagate? FIXME: Bug 408838. } if (!ok) { JS_ReportError(cx, "Error during JSON encoding"); return JS_FALSE; } output = jschar(isArray ? ']' : '}'); ok = callback(&output, 1, data); return ok; }