Commit 05212069 authored by Philip Chimento's avatar Philip Chimento 🚮

log: Add gjs_debug_id()

In order to prevent converting a jsid to a UTF-8 string, only for the
purpose of putting it in a debug message, we introduce gjs_debug_id() and
its friends gjs_debug_value(), gjs_debug_string(), and
gjs_debug_symbol(). If this seems like overkill just to avoid one UTF-8
conversion, it is; but these functions will be used later in the
structured logging code.
parent 8f1eb23f
......@@ -24,6 +24,9 @@
#include <config.h>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <string.h>
#include "jsapi-util.h"
......@@ -365,3 +368,133 @@ gjs_intern_string_to_id(JSContext *cx,
JS::RootedId id(cx, INTERNED_STRING_TO_JSID(cx, str));
return id;
}
static std::string
gjs_debug_flat_string(JSFlatString *fstr)
{
JSLinearString *str = js::FlatStringToLinearString(fstr);
size_t len = js::GetLinearStringLength(str);
JS::AutoCheckCannotGC nogc;
if (js::LinearStringHasLatin1Chars(str)) {
const JS::Latin1Char *chars = js::GetLatin1LinearStringChars(nogc, str);
return std::string(reinterpret_cast<const char *>(chars), len);
}
std::ostringstream out;
const char16_t *chars = js::GetTwoByteLinearStringChars(nogc, str);
for (size_t ix = 0; ix < len; ix++) {
char16_t c = chars[ix];
if (c == '\n')
out << "\\n";
else if (c == '\t')
out << "\\t";
else if (c >= 32 && c < 127)
out << c;
else if (c <= 255)
out << "\\x" << std::setfill('0') << std::setw(2) << unsigned(c);
else
out << "\\x" << std::setfill('0') << std::setw(4) << unsigned(c);
}
return out.str();
}
std::string
gjs_debug_string(JSString *str)
{
if (!JS_StringIsFlat(str)) {
std::ostringstream out("<non-flat string of length ");
out << JS_GetStringLength(str) << '>';
return out.str();
}
return gjs_debug_flat_string(JS_ASSERT_STRING_IS_FLAT(str));
}
std::string
gjs_debug_symbol(JS::Symbol * const sym)
{
/* This is OK because JS::GetSymbolCode() and JS::GetSymbolDescription()
* can't cause a garbage collection */
JS::HandleSymbol handle = JS::HandleSymbol::fromMarkedLocation(&sym);
JS::SymbolCode code = JS::GetSymbolCode(handle);
JSString *descr = JS::GetSymbolDescription(handle);
if (size_t(code) < JS::WellKnownSymbolLimit)
return gjs_debug_string(descr);
std::ostringstream out;
if (code == JS::SymbolCode::InSymbolRegistry) {
out << "Symbol.for(";
if (descr)
out << gjs_debug_string(descr);
else
out << "undefined";
out << ")";
return out.str();
}
if (code == JS::SymbolCode::UniqueSymbol) {
if (descr)
out << "Symbol(" << gjs_debug_string(descr) << ")";
else
out << "<Symbol at " << sym << ">";
return out.str();
}
out << "<unexpected symbol code " << uint32_t(code) << ">";
return out.str();
}
std::string
gjs_debug_value(JS::Value v)
{
std::ostringstream out;
if (v.isNull())
return "null";
if (v.isUndefined())
return "undefined";
if (v.isInt32()) {
out << v.toInt32();
return out.str();
}
if (v.isDouble()) {
out << v.toDouble();
return out.str();
}
if (v.isString()) {
out << gjs_debug_string(v.toString());
return out.str();
}
if (v.isSymbol()) {
out << gjs_debug_symbol(v.toSymbol());
return out.str();
}
if (v.isObject() && js::IsFunctionObject(&v.toObject())) {
JSFunction* fun = JS_GetObjectFunction(&v.toObject());
JSString *display_name = JS_GetFunctionDisplayId(fun);
if (display_name)
out << "<function " << gjs_debug_string(display_name);
else
out << "<unnamed function";
out << " at " << fun << '>';
return out.str();
}
if (v.isObject()) {
JSObject *obj = &v.toObject();
const JSClass* clasp = JS_GetClass(obj);
out << "<object " << clasp->name << " at " << obj << '>';
return out.str();
}
if (v.isBoolean())
return (v.toBoolean() ? "true" : "false");
if (v.isMagic())
return "<magic>";
return "unexpected value";
}
std::string
gjs_debug_id(jsid id)
{
if (JSID_IS_STRING(id))
return gjs_debug_flat_string(JSID_TO_FLAT_STRING(id));
return gjs_debug_value(js::IdToValue(id));
}
......@@ -138,15 +138,12 @@ throw_property_lookup_error(JSContext *cx,
/* remember gjs_throw() is a no-op if JS_GetProperty()
* already set an exception
*/
GjsAutoJSChar name;
gjs_get_string_id(cx, property_name, &name);
if (description)
gjs_throw(cx, "No property '%s' in %s (or %s)", name.get(), description,
reason);
gjs_throw(cx, "No property '%s' in %s (or %s)",
gjs_debug_id(property_name).c_str(), description, reason);
else
gjs_throw(cx, "No property '%s' in object %p (or %s)", name.get(),
obj.address(), reason);
gjs_throw(cx, "No property '%s' in object %p (or %s)",
gjs_debug_id(property_name).c_str(), obj.get(), reason);
}
/* Returns whether the object had the property; if the object did
......
......@@ -385,4 +385,9 @@ bool gjs_object_require_converted_property(JSContext *cx,
value);
}
std::string gjs_debug_string(JSString *str);
std::string gjs_debug_symbol(JS::Symbol * const sym);
std::string gjs_debug_value(JS::Value v);
std::string gjs_debug_id(jsid id);
#endif /* __GJS_JSAPI_UTIL_H__ */
......@@ -163,17 +163,14 @@ class GjsModule {
* be supported according to ES6. For compatibility with earlier GJS,
* we treat it as if it were a real property, but warn about it. */
GjsAutoJSChar prop_name;
if (!gjs_get_string_id(cx, id, &prop_name))
return false;
g_warning("Some code accessed the property '%s' on the module '%s'. "
"That property was defined with 'let' or 'const' inside the "
"module. This was previously supported, but is not correct "
"according to the ES6 standard. Any symbols to be exported "
"from a module must be defined with 'var'. The property "
"access will work as previously for the time being, but "
"please fix your code anyway.", prop_name.get(), m_name);
"please fix your code anyway.",
gjs_debug_id(id).c_str(), m_name);
JS::Rooted<JS::PropertyDescriptor> desc(cx);
return JS_GetPropertyDescriptorById(cx, lexical, id, &desc) &&
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment