Bug 1896505 - Track Promise Type II subclassing r=jandem

Differential Revision: https://phabricator.services.mozilla.com/D210536
This commit is contained in:
Matthew Gaudet 2024-05-29 19:14:12 +00:00
parent a906e69d18
commit 9942bafee7
9 changed files with 174 additions and 19 deletions

View File

@ -71,6 +71,7 @@ custom JS_asmjs uses asm.js
custom JS_wasm uses WebAssembly
custom JS_wasm_legacy_exceptions uses WebAssembly legacy exception-handling
custom JS_subclassing_array_type_2 Array is Type II subclassed
custom JS_subclassing_promise_type_2 Promise is Type II subclassed
// Console API
method console.assert

View File

@ -107,8 +107,8 @@ use.counter:
send_in_pings:
- use-counters
# Total of 2311 use counter metrics (excludes denominators).
# Total of 355 'page' use counters.
# Total of 2313 use counter metrics (excludes denominators).
# Total of 356 'page' use counters.
use.counter.page:
svgsvgelement_getelementbyid:
type: counter
@ -552,6 +552,23 @@ use.counter.page:
send_in_pings:
- use-counters
js_subclassing_promise_type_2:
type: counter
description: >
Whether a page Promise is Type II subclassed.
Compare against `use.counter.top_level_content_documents_destroyed`
to calculate the rate.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
notification_emails:
- dom-core@mozilla.com
- emilio@mozilla.com
expires: never
send_in_pings:
- use-counters
console_assert:
type: counter
description: >
@ -6145,7 +6162,7 @@ use.counter.page:
send_in_pings:
- use-counters
# Total of 355 'document' use counters.
# Total of 356 'document' use counters.
use.counter.doc:
svgsvgelement_getelementbyid:
type: counter
@ -6589,6 +6606,23 @@ use.counter.doc:
send_in_pings:
- use-counters
js_subclassing_promise_type_2:
type: counter
description: >
Whether a document Promise is Type II subclassed.
Compare against `use.counter.content_documents_destroyed`
to calculate the rate.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
notification_emails:
- dom-core@mozilla.com
- emilio@mozilla.com
expires: never
send_in_pings:
- use-counters
console_assert:
type: counter
description: >

View File

@ -84,11 +84,12 @@ using JSAccumulateTelemetryDataCallback = void (*)(JSMetric, uint32_t);
extern JS_PUBLIC_API void JS_SetAccumulateTelemetryCallback(
JSContext* cx, JSAccumulateTelemetryDataCallback callback);
#define FOR_EACH_JS_USE_COUNTER(_) \
_(ASMJS, AsmJS) \
_(WASM, Wasm) \
_(WASM_LEGACY_EXCEPTIONS, WasmLegacyExceptions) \
_(SUBCLASSING_ARRAY_TYPE_II, SubclassingArrayTypeII)
#define FOR_EACH_JS_USE_COUNTER(_) \
_(ASMJS, AsmJS) \
_(WASM, Wasm) \
_(WASM_LEGACY_EXCEPTIONS, WasmLegacyExceptions) \
_(SUBCLASSING_ARRAY_TYPE_II, SubclassingArrayTypeII) \
_(SUBCLASSING_PROMISE_TYPE_II, SubclassingPromiseTypeII)
/*
* Use counter names passed to the accumulate use counter callback.

View File

@ -1802,6 +1802,9 @@ CreatePromiseObjectWithoutResolutionFunctions(JSContext* cx) {
return true;
}
// At this point this is effectively subclassing;
ReportUsageCounter(cx, C, SUBCLASSING_PROMISE, SUBCLASSING_TYPE_II);
// Step 4. Let executorClosure be a new Abstract Closure with parameters
// (resolve, reject) that captures promiseCapability and performs the
// following steps when called:
@ -2791,6 +2794,12 @@ static bool PromiseConstructor(JSContext* cx, unsigned argc, Value* vp) {
return true;
}
bool js::IsPromiseConstructor(const JSObject* obj) {
// Note: this also returns true for cross-realm Promise constructors in the
// same compartment.
return IsNativeFunction(obj, PromiseConstructor);
}
/**
* ES2022 draft rev d03c1ec6e235a5180fa772b6178727c17974cb14
*

View File

@ -263,6 +263,7 @@ bool IsPromiseWithDefaultResolvingFunction(PromiseObject* promise);
void SetAlreadyResolvedPromiseWithDefaultResolvingFunction(
PromiseObject* promise);
bool IsPromiseConstructor(const JSObject* obj);
} // namespace js
#endif // builtin_Promise_h

View File

@ -130,7 +130,8 @@
// Support for usage counters around subclassing:
#define SUBCLASSING_ARRAY 1
#define SUBCLASSING_LAST_BUILTIN 2
#define SUBCLASSING_PROMISE 2
#define SUBCLASSING_LAST_BUILTIN 3
#define SUBCLASSING_TYPE_II 2
#define SUBCLASSING_TYPE_III 3
@ -141,5 +142,7 @@
#define SUBCLASSING_BUILTIN_SHIFT 16
#define SUBCLASS_ARRAY_TYPE_II \
((SUBCLASSING_ARRAY << SUBCLASSING_BUILTIN_SHIFT) | SUBCLASSING_TYPE_II)
#define SUBCLASS_PROMISE_TYPE_II \
((SUBCLASSING_PROMISE << SUBCLASSING_BUILTIN_SHIFT) | SUBCLASSING_TYPE_II)
#endif

View File

@ -0,0 +1,90 @@
function test_function_for_use_counter_integration(fn, counter, expected_growth = true) {
let before = getUseCounterResults();
assertEq(counter in before, true);
fn();
let after = getUseCounterResults();
if (expected_growth) {
console.log("Yes Increase: Before ", before[counter], " After", after[counter]);
assertEq(after[counter] > before[counter], true);
} else {
console.log("No Increase: Before ", before[counter], " After", after[counter]);
assertEq(after[counter] == before[counter], true);
}
}
class MyPromise extends Promise { }
function promise_all() {
let p = Promise.all([Promise.resolve(1)]);
assertEq(p instanceof Promise, true);
}
function promise_all_subclassing_type_ii() {
let p = MyPromise.all([Promise.resolve(1)]);
assertEq(p instanceof MyPromise, true);
}
test_function_for_use_counter_integration(promise_all, "SubclassingPromiseTypeII", /* expected_growth = */ false);
test_function_for_use_counter_integration(promise_all_subclassing_type_ii, "SubclassingPromiseTypeII", /* expected_growth = */ true);
function promise_allSettled() {
let p = Promise.allSettled([Promise.resolve(1)]);
assertEq(p instanceof Promise, true);
}
function promise_allSettled_subclassing_type_ii() {
let p = MyPromise.allSettled([Promise.resolve(1)]);
assertEq(p instanceof MyPromise, true);
}
test_function_for_use_counter_integration(promise_allSettled, "SubclassingPromiseTypeII", /* expected_growth = */ false);
test_function_for_use_counter_integration(promise_allSettled_subclassing_type_ii, "SubclassingPromiseTypeII", /* expected_growth = */ true);
function promise_any() {
let p = Promise.any([Promise.resolve(1)]);
assertEq(p instanceof Promise, true);
}
function promise_any_subclassing_type_ii() {
let p = MyPromise.any([Promise.resolve(1)]);
assertEq(p instanceof MyPromise, true);
}
test_function_for_use_counter_integration(promise_any, "SubclassingPromiseTypeII", /* expected_growth = */ false);
test_function_for_use_counter_integration(promise_any_subclassing_type_ii, "SubclassingPromiseTypeII", /* expected_growth = */ true);
function promise_race() {
let p = Promise.race([Promise.resolve(1)]);
assertEq(p instanceof Promise, true);
}
function promise_race_subclassing_type_ii() {
let p = MyPromise.race([Promise.resolve(1)]);
assertEq(p instanceof MyPromise, true);
}
test_function_for_use_counter_integration(promise_race, "SubclassingPromiseTypeII", /* expected_growth = */ false);
test_function_for_use_counter_integration(promise_race_subclassing_type_ii, "SubclassingPromiseTypeII", /* expected_growth = */ true);
function promise_resolve() {
let p = Promise.resolve(1)
assertEq(p instanceof Promise, true);
}
function promise_resolve_subclassing_type_ii() {
let p = MyPromise.resolve(1)
assertEq(p instanceof MyPromise, true);
}
test_function_for_use_counter_integration(promise_resolve, "SubclassingPromiseTypeII", /* expected_growth = */ false);
test_function_for_use_counter_integration(promise_resolve_subclassing_type_ii, "SubclassingPromiseTypeII", /* expected_growth = */ true);
function promise_reject() {
let p = Promise.reject(1).catch(() => undefined)
assertEq(p instanceof Promise, true);
}
function promise_reject_subclassing_type_ii() {
let p = MyPromise.reject(1).catch(() => undefined)
assertEq(p instanceof MyPromise, true);
}
test_function_for_use_counter_integration(promise_reject, "SubclassingPromiseTypeII", /* expected_growth = */ false);
test_function_for_use_counter_integration(promise_reject_subclassing_type_ii, "SubclassingPromiseTypeII", /* expected_growth = */ true);

View File

@ -2966,13 +2966,8 @@ bool js::ReportUsageCounter(JSContext* cx, HandleObject constructor,
switch (builtin) {
case SUBCLASSING_ARRAY: {
// Check if the provided function is actually the array constructor
// anyhow; We're interested in if the object is in the current realm; CCW
// realm is OK because even if this is a CCW it'll fail the
// IsArrayConstructor check.
//
// Constructor may be nullptr if check has already been done.
if (constructor && IsArrayConstructor(constructor) &&
constructor->maybeCCWRealm() == cx->realm()) {
// anyhow; Constructor may be nullptr if check has already been done.
if (constructor && IsArrayConstructor(constructor)) {
return true;
}
switch (type) {
@ -2984,6 +2979,19 @@ bool js::ReportUsageCounter(JSContext* cx, HandleObject constructor,
MOZ_CRASH("Unexpected Subclassing Type");
}
}
case SUBCLASSING_PROMISE: {
if (constructor && IsPromiseConstructor(constructor)) {
return true;
}
switch (type) {
case SUBCLASSING_TYPE_II:
cx->runtime()->setUseCounter(
cx->global(), JSUseCounter::SUBCLASSING_PROMISE_TYPE_II);
return true;
default:
MOZ_CRASH("Unexpected Subclassing Type");
}
}
default:
MOZ_CRASH("Unexpected builtin");
};
@ -3007,9 +3015,13 @@ bool js::ReportUsageCounter(JSContext* cx, HandleObject constructor,
//
// This produces the following magic constant values:
//
// Array Subclassing Type II: (1 << 16) | 2 == 0x010002
// Array Subclassing Type III: (1 << 16) | 3 == 0x010003
// Array Subclassing Type IV: (1 << 16) | 4 == 0x010004
// Array Subclassing Type II: (1 << 16) | 2 == 0x010002
// Array Subclassing Type III: (1 << 16) | 3 == 0x010003
// Array Subclassing Type IV: (1 << 16) | 4 == 0x010000
//
// Promise Subclassing Type II: (2 << 16) | 2 == 0x020002
// Promise Subclassing Type III: (2 << 16) | 3 == 0x020003
// Promise Subclassing Type IV: (2 << 16) | 4 == 0x020004
//
// Subclassing is reported iff the constructor provided doesn't match
// the existing prototype.

View File

@ -2639,6 +2639,10 @@ static void SetUseCounterCallback(JSObject* obj, JSUseCounter counter) {
SetUseCounter(obj,
mozilla::eUseCounter_custom_JS_subclassing_array_type_2);
return;
case JSUseCounter::SUBCLASSING_PROMISE_TYPE_II:
SetUseCounter(obj,
mozilla::eUseCounter_custom_JS_subclassing_promise_type_2);
return;
case JSUseCounter::COUNT:
break;
}