Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions JSTests/stress/generator-and-async-body-function-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//@ requireOptions("--useAsyncStackTrace=1")

// The code of a generator, of an async generator and of an async function that awaits runs in a body function. The
// frame of that code takes its name from the body function. The body function takes the name of the function: the
// declared one, or the one inferred from the binding the function is assigned to.

let sink = 0; // `sink++` after a call keeps that call out of tail position.

function shouldBe(actual, expected, message) {
if (actual !== expected)
throw new Error(`${message}: expected ${expected} but got ${actual}`);
}

// The names of the functions in a stack, down to main().
function frames(stack) {
const names = [];
for (const line of stack.split("\n")) {
if (!line)
continue;
const name = line.slice(0, line.indexOf("@"));
if (name === "main" || name === "async main")
break;
names.push(name);
}
return names.join(",");
}

const arrow = async () => {
await null;
const stack = new Error().stack;
sink++;
return stack;
};
const arrowBeforeAwait = async () => {
const stack = new Error().stack;
sink++;
await null;
return stack;
};
const expression = async function () {
await null;
const stack = new Error().stack;
sink++;
return stack;
};
const object = {
property: async () => {
await null;
const stack = new Error().stack;
sink++;
return stack;
},
};
class Class {
static field = async () => {
await null;
const stack = new Error().stack;
sink++;
return stack;
};
}
let assigned;
assigned = async () => {
await null;
const stack = new Error().stack;
sink++;
return stack;
};
function makeNested() {
const nested = async () => {
await null;
const stack = new Error().stack;
sink++;
return stack;
};
sink++;
return nested;
}
const generator = function* () {
yield new Error().stack;
};
const asyncGenerator = async function* () {
yield new Error().stack;
};
// `awaits` is not running when the stack is made: it awaits `declaration`.
const awaits = async () => {
const stack = await declaration();
sink++;
return stack;
};
async function declaration() {
await null;
const stack = new Error().stack;
sink++;
return stack;
}
// No name can be inferred for these.
const inArray = [async () => {
await null;
const stack = new Error().stack;
sink++;
return stack;
}];
const key = "comp" + "uted";
const computed = {
[key]: async () => {
await null;
const stack = new Error().stack;
sink++;
return stack;
},
};

async function main() {
shouldBe(frames(await arrow()), "arrow", "arrow");
shouldBe(frames(await arrowBeforeAwait()), "arrowBeforeAwait", "arrow before its first await");
Comment thread
robobun marked this conversation as resolved.
Resolved
shouldBe(frames(await expression()), "expression", "function expression");
shouldBe(frames(await object.property()), "property", "property");
shouldBe(frames(await Class.field()), "field", "class field");
shouldBe(frames(await assigned()), "assigned", "assignment");
shouldBe(frames(await makeNested()()), "nested", "nested");
shouldBe(frames(generator().next().value), "generator", "generator");
shouldBe(frames((await asyncGenerator().next()).value), "asyncGenerator,next", "async generator");
shouldBe(frames(await awaits()), "declaration,async awaits", "awaiting arrow");
shouldBe(frames(await declaration()), "declaration", "declaration");
shouldBe(frames(await inArray[0]()), "", "array element");
shouldBe(frames(await computed[key]()), "", "computed key");
sink++;
}

// The name of a frame comes from the executable of its function, whichever tier runs it: one run is enough.
let error;
main().then(() => { sink++; }, rejection => { error = rejection; });
drainMicrotasks();
if (error)
throw error;
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ function drain(promise) {
async function* asyncGeneratorBody() { return callerName(); }
shouldBe(drain(asyncGeneratorBody().next()).value, "asyncGeneratorBody");

// A resumed async arrow body carries no name of its own, so the point of this one is only
// that a frame is there at all: without it the caller would be drainMicrotasks.
// A resumed async arrow body has the name that was inferred for the arrow. Without its frame
// the caller would be drainMicrotasks.
var asyncArrowBody = async () => { await 0; return callerName(); };
shouldBe(drain(asyncArrowBody()), "");
shouldBe(drain(asyncArrowBody()), "asyncArrowBody");

function ordinaryStrictFunction() { return callerName(); }
shouldBe(ordinaryStrictFunction(), "bodiesKeepTheirFrame");
Expand Down
13 changes: 13 additions & 0 deletions Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,21 @@ static UnlinkedFunctionCodeBlock* generateUnlinkedFunctionCodeBlock(
JSParserScriptMode scriptMode = executable->scriptMode();
ASSERT(isFunctionParseMode(executable->parseMode()));
auto* classElementDefinitions = executable->classElementDefinitions();
#if USE(BUN_JSC_ADDITIONS)
// The parser names the body function of a generator or async function after the function. Without a declared name
// that is the inferred one (`const f = async () => { }`), unless it is a private name (`export default` is `*default*`).
const Identifier* inferredName = nullptr;
if (isGeneratorOrAsyncFunctionWrapperParseMode(executable->parseMode()) && executable->name().isNull()) {
const Identifier& ecmaName = executable->ecmaName();
if (!ecmaName.isEmpty() && !ecmaName.isSymbol())
inferredName = &ecmaName;
}
std::unique_ptr<FunctionNode> function = parse<FunctionNode>(
vm, source, executable->name(), executable->implementationVisibility(), builtinMode, executable->lexicallyScopedFeatures(), scriptMode, executable->parseMode(), executable->functionMode(), executable->superBinding(), error, executable->constructorKind(), executable->derivedContextType(), EvalContextType::None, nullptr, classElementDefinitions, false, inferredName);
#else
std::unique_ptr<FunctionNode> function = parse<FunctionNode>(
vm, source, executable->name(), executable->implementationVisibility(), builtinMode, executable->lexicallyScopedFeatures(), scriptMode, executable->parseMode(), executable->functionMode(), executable->superBinding(), error, executable->constructorKind(), executable->derivedContextType(), EvalContextType::None, nullptr, classElementDefinitions);
#endif

if (!function) {
ASSERT(error.isValid());
Expand Down
14 changes: 11 additions & 3 deletions Source/JavaScriptCore/parser/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,19 +263,27 @@ std::expected<typename Parser<LexerType>::ParseInnerResult, String> Parser<Lexer
if (m_lexer->isReparsingFunction())
m_statementDepth--;

#if USE(BUN_JSC_ADDITIONS)
// The body function of a generator or async function takes the name of the function, for the frames that run its
// code: the declared name, or the one that was inferred (`const f = async () => { }`), which is not a callee name.
const Identifier& bodyName = calleeName.isNull() && m_inferredFunctionName ? *m_inferredFunctionName : calleeName;
Comment thread
robobun marked this conversation as resolved.
Resolved
#else
const Identifier& bodyName = calleeName;
#endif

SourceElements* sourceElements = nullptr;
// The only way we can error this early is if we reparse a function and we run out of stack space.
if (!hasError()) {
if (isAsyncFunctionWrapperParseMode(parseMode))
sourceElements = parseAsyncFunctionSourceElements(context, calleeName, isArrowFunctionBodyExpression, CheckForStrictMode);
sourceElements = parseAsyncFunctionSourceElements(context, bodyName, isArrowFunctionBodyExpression, CheckForStrictMode);
else if (isArrowFunctionBodyExpression)
sourceElements = parseArrowFunctionSingleExpressionBodySourceElements(context);
else if (isModuleParseMode(parseMode))
sourceElements = parseModuleSourceElements(context);
else if (isGeneratorWrapperParseMode(parseMode))
sourceElements = parseGeneratorFunctionSourceElements(context, calleeName, CheckForStrictMode);
sourceElements = parseGeneratorFunctionSourceElements(context, bodyName, CheckForStrictMode);
else if (isAsyncGeneratorWrapperParseMode(parseMode))
sourceElements = parseAsyncGeneratorFunctionSourceElements(context, calleeName, isArrowFunctionBodyExpression, CheckForStrictMode);
sourceElements = parseAsyncGeneratorFunctionSourceElements(context, bodyName, isArrowFunctionBodyExpression, CheckForStrictMode);
else if (parsingContext == ParsingContext::FunctionConstructor)
sourceElements = parseSingleFunction(context, functionConstructorParametersEndPosition);
else if (parseMode == SourceParseMode::ClassFieldInitializerMode) {
Expand Down
18 changes: 18 additions & 0 deletions Source/JavaScriptCore/parser/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,10 @@ class JSC_CACHE_LINE_ALIGNED Parser {
std::unique_ptr<ParsedNode> parse(ParserError&, const Identifier&, ParsingContext, std::optional<int> functionConstructorParametersEndPosition = std::nullopt, const PrivateNameEnvironment* = nullptr, const FixedVector<UnlinkedFunctionExecutable::ClassElementDefinition>* = nullptr);

void overrideConstructorKindForTopLevelFunctionExpressions(ConstructorKind constructorKind) { m_constructorKindForTopLevelFunctionExpressions = constructorKind; }
#if USE(BUN_JSC_ADDITIONS)
// The name that was inferred for the function this parser reparses (`const f = async () => { }`). See parseInner().
void setInferredFunctionName(const Identifier* name) { m_inferredFunctionName = name; }
#endif

JSTokenLocation locationBeforeLastToken() const { return m_lastTokenLocation; }

Expand Down Expand Up @@ -2159,6 +2163,9 @@ class JSC_CACHE_LINE_ALIGNED Parser {
SuperBinding m_superBinding;
bool m_hasStackOverflow;
ScopeStack m_scopeStack;
#if USE(BUN_JSC_ADDITIONS)
const Identifier* m_inferredFunctionName { nullptr };
#endif

static void verifyLayout();
};
Expand Down Expand Up @@ -2285,7 +2292,12 @@ std::unique_ptr<ParsedNode> parse(
EvalContextType evalContextType = EvalContextType::None,
const PrivateNameEnvironment* parentScopePrivateNames = nullptr,
const FixedVector<UnlinkedFunctionExecutable::ClassElementDefinition>* classElementDefinitions = nullptr,
#if USE(BUN_JSC_ADDITIONS)
bool isInsideOrdinaryFunction = false,
const Identifier* inferredName = nullptr)
#else
bool isInsideOrdinaryFunction = false)
#endif
{
ASSERT(!source.provider()->source().isNull());

Expand All @@ -2296,6 +2308,9 @@ std::unique_ptr<ParsedNode> parse(
std::unique_ptr<ParsedNode> result;
if (source.provider()->source().is8Bit()) {
Parser<Lexer<Latin1Character>> parser(vm, source, implementationVisibility, builtinMode, lexicallyScopedFeatures, scriptMode, parseMode, functionMode, superBinding, constructorKind, derivedContextType, isEvalNode<ParsedNode>(), evalContextType, nullptr, isInsideOrdinaryFunction);
#if USE(BUN_JSC_ADDITIONS)
parser.setInferredFunctionName(inferredName);
#endif
result = parser.parse<ParsedNode>(error, name, ParsingContext::Normal, std::nullopt, parentScopePrivateNames, classElementDefinitions);
if (builtinMode == JSParserBuiltinMode::Builtin) {
if (!result) {
Expand All @@ -2306,6 +2321,9 @@ std::unique_ptr<ParsedNode> parse(
}
} else {
Parser<Lexer<char16_t>> parser(vm, source, implementationVisibility, builtinMode, lexicallyScopedFeatures, scriptMode, parseMode, functionMode, superBinding, constructorKind, derivedContextType, isEvalNode<ParsedNode>(), evalContextType, nullptr, isInsideOrdinaryFunction);
#if USE(BUN_JSC_ADDITIONS)
parser.setInferredFunctionName(inferredName);
#endif
result = parser.parse<ParsedNode>(error, name, ParsingContext::Normal, std::nullopt, parentScopePrivateNames, classElementDefinitions);
}

Expand Down
Loading