Skip to content

Commit 7a74d76

Browse files
author
Guy Bedford
authored
fix: console logging support improvements (#434)
Updates the console logging builtin with various improvements, specifically: * Array logging * Quoting strings * Object spacing * Functions and function names properly shown, but skipped if they are not own properties * Getter and Setter properties shown but not triggered, also skipped if not own properties * Class names are displayed for all objects with a constructor * TypedArrays log as arrays, along with the array type
1 parent 5386fcc commit 7a74d76

File tree

3 files changed

+205
-102
lines changed

3 files changed

+205
-102
lines changed

c-dependencies/js-compute-runtime/builtins/shared/console.cpp

Lines changed: 181 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
#include "console.h"
2-
#include "mozilla/Result.h"
2+
#include <js/Array.h>
3+
#include <js/PropertyAndElement.h>
4+
#pragma clang diagnostic push
5+
#pragma clang diagnostic ignored "-Winvalid-offsetof"
6+
#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"
7+
#include <js/experimental/TypedData.h>
8+
#pragma clang diagnostic pop
39

410
JS::Result<mozilla::Ok> ToSource(JSContext *cx, std::string &sourceOut, JS::HandleValue val,
511
JS::MutableHandleObjectVector visitedObjects);
@@ -23,18 +29,14 @@ JS::Result<mozilla::Ok> PromiseToSource(JSContext *cx, std::string &sourceOut, J
2329
}
2430
case JS::PromiseState::Fulfilled: {
2531
JS::RootedValue value(cx, JS::GetPromiseResult(obj));
26-
std::string source;
27-
MOZ_TRY(ToSource(cx, source, value, visitedObjects));
28-
sourceOut += source;
32+
MOZ_TRY(ToSource(cx, sourceOut, value, visitedObjects));
2933
sourceOut += " }";
3034
break;
3135
}
3236
case JS::PromiseState::Rejected: {
3337
sourceOut += "<rejected> ";
3438
JS::RootedValue value(cx, JS::GetPromiseResult(obj));
35-
std::string source;
36-
MOZ_TRY(ToSource(cx, source, value, visitedObjects));
37-
sourceOut += source;
39+
MOZ_TRY(ToSource(cx, sourceOut, value, visitedObjects));
3840
sourceOut += " }";
3941
break;
4042
}
@@ -84,13 +86,9 @@ JS::Result<mozilla::Ok> MapToSource(JSContext *cx, std::string &sourceOut, JS::H
8486
entry = &entry_val.toObject();
8587
JS_GetElement(cx, entry, 0, &name_val);
8688
JS_GetElement(cx, entry, 1, &value_val);
87-
std::string name;
88-
MOZ_TRY(ToSource(cx, name, name_val, visitedObjects));
89-
sourceOut += name;
89+
MOZ_TRY(ToSource(cx, sourceOut, name_val, visitedObjects));
9090
sourceOut += " => ";
91-
std::string value;
92-
MOZ_TRY(ToSource(cx, value, value_val, visitedObjects));
93-
sourceOut += value;
91+
MOZ_TRY(ToSource(cx, sourceOut, value_val, visitedObjects));
9492
}
9593
sourceOut += " }";
9694
return mozilla::Ok();
@@ -126,73 +124,138 @@ JS::Result<mozilla::Ok> SetToSource(JSContext *cx, std::string &sourceOut, JS::H
126124
if (done) {
127125
break;
128126
}
129-
std::string entry;
130-
MOZ_TRY(ToSource(cx, entry, entry_val, visitedObjects));
131127
if (firstValue) {
132128
firstValue = false;
133129
} else {
134130
sourceOut += ", ";
135131
}
136-
sourceOut += entry;
132+
MOZ_TRY(ToSource(cx, sourceOut, entry_val, visitedObjects));
137133
}
138134
sourceOut += " }";
139135
return mozilla::Ok();
140136
}
141137

142-
/**
143-
* Turn a handle of an Object into a string which represents the object.
144-
* This function will go through every property on the object (including non-enumerable properties)
145-
* Each property name and property value within the object will be converted into it's ToSource
146-
* representation. Note: functions and methods within the object are not included in the output
147-
*
148-
* E.G. The object `{ a: 1, b: 2, c: 3, d(){}, get f(){}, g: function bar() {} }`
149-
* would be represented as "{a: 1, b: {c: 2}, c: 3, f: undefined}"
138+
JS::Result<mozilla::Ok> ArrayToSource(JSContext *cx, std::string &sourceOut, JS::HandleObject obj,
139+
JS::MutableHandleObjectVector visitedObjects) {
140+
sourceOut += "[";
141+
uint32_t len;
142+
if (!JS::GetArrayLength(cx, obj, &len)) {
143+
return JS::Result<mozilla::Ok>(JS::Error());
144+
}
145+
146+
for (int i = 0; i < len; i++) {
147+
JS::RootedValue entry_val(cx);
148+
JS_GetElement(cx, obj, i, &entry_val);
149+
if (i > 0) {
150+
sourceOut += ", ";
151+
}
152+
MOZ_TRY(ToSource(cx, sourceOut, entry_val, visitedObjects));
153+
}
154+
sourceOut += "]";
155+
return mozilla::Ok();
156+
}
157+
158+
/*
159+
* Logs all enumerable properties, except non-own function and symbol properties
160+
* Includes handling for getters and setters
150161
*/
151162
JS::Result<mozilla::Ok> ObjectToSource(JSContext *cx, std::string &sourceOut, JS::HandleObject obj,
152163
JS::MutableHandleObjectVector visitedObjects) {
153-
sourceOut += "{";
154164
JS::RootedIdVector ids(cx);
165+
155166
if (!js::GetPropertyKeys(cx, obj, 0, &ids)) {
156167
return JS::Result<mozilla::Ok>(JS::Error());
157168
}
158169

159170
JS::RootedValue value(cx);
160171
size_t length = ids.length();
172+
173+
sourceOut += "{";
161174
bool firstValue = true;
162175
for (size_t i = 0; i < length; ++i) {
163176
const auto &id = ids[i];
164-
if (!JS_GetPropertyById(cx, obj, id, &value)) {
165-
return JS::Result<mozilla::Ok>(JS::Error());
177+
178+
JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(cx);
179+
JS_GetOwnPropertyDescriptorById(cx, obj, id, &desc);
180+
181+
bool getter_setter = !desc.isNothing() && (desc->hasGetter() || desc->hasSetter());
182+
183+
// retrive the value if not a getter or setter
184+
if (!getter_setter) {
185+
if (!JS_GetPropertyById(cx, obj, id, &value)) {
186+
return JS::Result<mozilla::Ok>(JS::Error());
187+
}
166188
}
167189

168-
if (!value.isObject() || !JS_ObjectIsFunction(&value.toObject())) {
169-
if (firstValue) {
170-
firstValue = false;
171-
} else {
172-
sourceOut += ", ";
190+
// Skip logging non-own function or getter and setter keys
191+
if (getter_setter || (value.isObject() && JS_ObjectIsFunction(&value.toObject()))) {
192+
bool own_prop;
193+
if (!JS_HasOwnPropertyById(cx, obj, id, &own_prop)) {
194+
return JS::Result<mozilla::Ok>(JS::Error());
173195
}
174-
if (id.isSymbol()) {
175-
JS::RootedValue v(cx, SymbolValue(id.toSymbol()));
176-
std::string source;
177-
MOZ_TRY(ToSource(cx, source, v, visitedObjects));
178-
sourceOut += source;
179-
} else {
180-
JS::RootedValue idValue(cx, js::IdToValue(id));
181-
std::string source;
182-
MOZ_TRY(ToSource(cx, source, idValue, visitedObjects));
183-
sourceOut += source;
196+
if (!own_prop) {
197+
continue;
184198
}
185-
sourceOut += ": ";
186-
std::string source;
187-
MOZ_TRY(ToSource(cx, source, value, visitedObjects));
188-
sourceOut += source;
199+
}
200+
201+
if (firstValue) {
202+
firstValue = false;
203+
sourceOut += " ";
204+
} else {
205+
sourceOut += ", ";
206+
}
207+
208+
// Key
209+
if (id.isSymbol()) {
210+
JS::RootedValue v(cx, SymbolValue(id.toSymbol()));
211+
MOZ_TRY(ToSource(cx, sourceOut, v, visitedObjects));
212+
} else {
213+
size_t message_len;
214+
JS::RootedValue v(cx, js::IdToValue(id));
215+
auto msg = encode(cx, v, &message_len);
216+
if (!msg) {
217+
return JS::Result<mozilla::Ok>(JS::Error());
218+
}
219+
sourceOut += std::string(msg.get(), message_len);
220+
}
221+
222+
sourceOut += ": ";
223+
224+
// Getters and Setters
225+
if (getter_setter) {
226+
sourceOut += "[Getter]";
227+
} else {
228+
MOZ_TRY(ToSource(cx, sourceOut, value, visitedObjects));
189229
}
190230
}
191231

232+
if (!firstValue) {
233+
sourceOut += " ";
234+
}
192235
sourceOut += "}";
236+
193237
return mozilla::Ok();
194238
}
195239

240+
mozilla::Maybe<std::string> get_class_name(JSContext *cx, JS::HandleObject obj) {
241+
mozilla::Maybe<std::string> result = {};
242+
JS::RootedValue constructorVal(cx);
243+
if (JS_GetProperty(cx, obj, "constructor", &constructorVal) && constructorVal.isObject()) {
244+
JS::RootedValue name(cx);
245+
JS::RootedObject constructorObj(cx, &constructorVal.toObject());
246+
if (JS_GetProperty(cx, constructorObj, "name", &name) && name.isString()) {
247+
size_t message_len;
248+
auto msg = encode(cx, name, &message_len);
249+
if (!msg) {
250+
return result;
251+
}
252+
std::string name_str(msg.get(), message_len);
253+
result.emplace(name_str);
254+
}
255+
}
256+
return result;
257+
}
258+
196259
/**
197260
* Turn a handle of any value into a string which represents it.
198261
*/
@@ -212,6 +275,35 @@ JS::Result<mozilla::Ok> ToSource(JSContext *cx, std::string &sourceOut, JS::Hand
212275
case JS::ValueType::Object: {
213276
JS::RootedObject obj(cx, &val.toObject());
214277

278+
if (JS_ObjectIsFunction(obj)) {
279+
sourceOut += "[Function";
280+
std::string source;
281+
auto id = JS_GetFunctionId(JS_ValueToFunction(cx, val));
282+
if (id) {
283+
sourceOut += " ";
284+
JS::RootedString name(cx, id);
285+
size_t name_len;
286+
auto msg = encode(cx, name, &name_len);
287+
if (!msg) {
288+
return JS::Result<mozilla::Ok>(JS::Error());
289+
}
290+
sourceOut += std::string(msg.get(), name_len);
291+
}
292+
sourceOut += "]";
293+
return mozilla::Ok();
294+
}
295+
296+
if (JS_IsTypedArrayObject(obj)) {
297+
// Show the typed array type
298+
mozilla::Maybe<std::string> name_str = get_class_name(cx, obj);
299+
if (!name_str.isNothing()) {
300+
sourceOut += *name_str;
301+
sourceOut += " ";
302+
}
303+
MOZ_TRY(ArrayToSource(cx, sourceOut, obj, visitedObjects));
304+
return mozilla::Ok();
305+
}
306+
215307
for (const auto &curObject : visitedObjects) {
216308
if (obj.get() == curObject) {
217309
sourceOut += "<Circular>";
@@ -223,68 +315,74 @@ JS::Result<mozilla::Ok> ToSource(JSContext *cx, std::string &sourceOut, JS::Hand
223315
return JS::Result<mozilla::Ok>(JS::Error());
224316
}
225317

226-
if (JS_ObjectIsFunction(obj)) {
227-
sourceOut += JS::InformalValueTypeName(val);
228-
return mozilla::Ok();
229-
}
230318
js::ESClass cls;
231319
if (!JS::GetBuiltinClass(cx, obj, &cls)) {
232320
return JS::Result<mozilla::Ok>(JS::Error());
233321
}
234322

235-
if (cls == js::ESClass::Array || cls == js::ESClass::Date || cls == js::ESClass::Error ||
236-
cls == js::ESClass::RegExp) {
323+
switch (cls) {
324+
case js::ESClass::Date:
325+
case js::ESClass::Error:
326+
case js::ESClass::RegExp: {
237327
JS::RootedString source(cx, JS_ValueToSource(cx, val));
238328
size_t message_len;
239329
auto msg = encode(cx, source, &message_len);
240330
if (!msg) {
241331
return JS::Result<mozilla::Ok>(JS::Error());
242332
}
243-
std::string sourceString(msg.get(), message_len);
244-
sourceOut += sourceString;
333+
sourceOut += std::string(msg.get(), message_len);
245334
return mozilla::Ok();
246-
} else if (cls == js::ESClass::Set) {
247-
std::string sourceString;
248-
MOZ_TRY(SetToSource(cx, sourceString, obj, visitedObjects));
249-
sourceOut += sourceString;
335+
}
336+
case js::ESClass::Array: {
337+
MOZ_TRY(ArrayToSource(cx, sourceOut, obj, visitedObjects));
250338
return mozilla::Ok();
251-
} else if (cls == js::ESClass::Map) {
252-
std::string sourceString;
253-
MOZ_TRY(MapToSource(cx, sourceString, obj, visitedObjects));
254-
sourceOut += sourceString;
339+
}
340+
case js::ESClass::Set: {
341+
MOZ_TRY(SetToSource(cx, sourceOut, obj, visitedObjects));
255342
return mozilla::Ok();
256-
} else if (cls == js::ESClass::Promise) {
257-
std::string sourceString;
258-
MOZ_TRY(PromiseToSource(cx, sourceString, obj, visitedObjects));
259-
sourceOut += sourceString;
343+
}
344+
case js::ESClass::Map: {
345+
MOZ_TRY(MapToSource(cx, sourceOut, obj, visitedObjects));
260346
return mozilla::Ok();
261-
} else {
347+
}
348+
case js::ESClass::Promise: {
349+
MOZ_TRY(PromiseToSource(cx, sourceOut, obj, visitedObjects));
350+
return mozilla::Ok();
351+
}
352+
default: {
353+
std::string sourceString;
262354
if (JS::IsWeakMapObject(obj)) {
263-
std::string sourceString = "WeakMap { <items unknown> }";
264-
sourceOut += sourceString;
355+
sourceOut += "WeakMap { <items unknown> }";
265356
return mozilla::Ok();
266357
}
267358
auto cls = JS::GetClass(obj);
268359
std::string className(cls->name);
269360
if (className == "WeakSet") {
270-
std::string sourceString = "WeakSet { <items unknown> }";
271-
sourceOut += sourceString;
361+
sourceOut += "WeakSet { <items unknown> }";
272362
return mozilla::Ok();
273363
}
274-
std::string sourceString;
275-
MOZ_TRY(ObjectToSource(cx, sourceString, obj, visitedObjects));
276-
sourceOut += sourceString;
364+
365+
// Lookup the class name if a custom class
366+
mozilla::Maybe<std::string> name_str = get_class_name(cx, obj);
367+
if (!name_str.isNothing() && *name_str != "Object") {
368+
sourceOut += *name_str;
369+
sourceOut += " ";
370+
}
371+
372+
MOZ_TRY(ObjectToSource(cx, sourceOut, obj, visitedObjects));
277373
return mozilla::Ok();
278374
}
375+
}
279376
}
280377
case JS::ValueType::String: {
281378
size_t message_len;
282379
auto msg = encode(cx, val, &message_len);
283380
if (!msg) {
284381
return JS::Result<mozilla::Ok>(JS::Error());
285382
}
286-
std::string sourceString(msg.get(), message_len);
287-
sourceOut += sourceString;
383+
sourceOut += '"';
384+
sourceOut += std::string(msg.get(), message_len);
385+
sourceOut += '"';
288386
return mozilla::Ok();
289387
}
290388
default: {
@@ -294,8 +392,7 @@ JS::Result<mozilla::Ok> ToSource(JSContext *cx, std::string &sourceOut, JS::Hand
294392
if (!msg) {
295393
return JS::Result<mozilla::Ok>(JS::Error());
296394
}
297-
std::string sourceString(msg.get(), message_len);
298-
sourceOut += sourceString;
395+
sourceOut += std::string(msg.get(), message_len);
299396
return mozilla::Ok();
300397
}
301398
}
@@ -316,12 +413,15 @@ static bool console_out(JSContext *cx, unsigned argc, JS::Value *vp) {
316413
if (result.isErr()) {
317414
return false;
318415
}
319-
std::string message = source;
416+
// strip quotes for direct string logs
417+
if (source[0] == '"' && source[source.length() - 1] == '"') {
418+
source = source.substr(1, source.length() - 2);
419+
}
320420
if (fullLogLine.length()) {
321421
fullLogLine += " ";
322-
fullLogLine += message;
422+
fullLogLine += source;
323423
} else {
324-
fullLogLine += message;
424+
fullLogLine += source;
325425
}
326426
}
327427

integration-tests/js-compute/fixtures/console/bin/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,6 @@ addEventListener("fetch", () => {
8787
console.log('TransformStream:', arg)
8888
arg = new WritableStream
8989
console.log('WritableStream:', arg)
90+
arg = new URL('https://www.test.com:123/asdf?some&params=val')
91+
console.log('URL:', arg)
9092
});

0 commit comments

Comments
 (0)