diff --git a/include-wasm/cjs-module-lexer.h b/include-wasm/cjs-module-lexer.h index ab2adca..54da2f0 100755 --- a/include-wasm/cjs-module-lexer.h +++ b/include-wasm/cjs-module-lexer.h @@ -21,6 +21,7 @@ struct StarExportBinding { const uint16_t* specifier_end; const uint16_t* id_start; const uint16_t* id_end; + struct StarExportBinding* next; }; typedef struct StarExportBinding StarExportBinding; @@ -43,7 +44,8 @@ const uint16_t* sa (uint32_t utf16Len) { const uint16_t* sourceEnd = source + utf16Len + 1; // ensure source is null terminated *(uint16_t*)(source + utf16Len) = '\0'; - analysis_base = (void*)sourceEnd; + const uintptr_t alignment = _Alignof(Slice); + analysis_base = (void*)(((uintptr_t)sourceEnd + alignment - 1) & ~(alignment - 1)); analysis_head = analysis_base; first_export = NULL; export_write_head = NULL; diff --git a/lib/lexer.wasm b/lib/lexer.wasm index dc29138..4673ec2 100755 Binary files a/lib/lexer.wasm and b/lib/lexer.wasm differ diff --git a/src/lexer.c b/src/lexer.c index fd3f1cf..d91a774 100755 --- a/src/lexer.c +++ b/src/lexer.c @@ -6,8 +6,6 @@ const uint16_t __empty_char = '\0'; const uint16_t* EMPTY_CHAR = &__empty_char; // tracked depth of template and brackets #define STACK_DEPTH 2048 -// tracked number of star exports -#define MAX_STAR_EXPORTS 256 const uint16_t* source; bool lastSlashWasDivision; @@ -20,7 +18,6 @@ uint16_t* pos; uint16_t* end; uint16_t* templateStack; uint16_t** openTokenPosStack; -StarExportBinding* starExportStack; bool nextBraceIsClass; uint16_t* lastReexportStart; @@ -36,8 +33,9 @@ uint32_t sourceLen; uint16_t templateStack_[STACK_DEPTH]; uint16_t* openTokenPosStack_[STACK_DEPTH]; bool openClassPosStack[STACK_DEPTH]; -StarExportBinding starExportStack_[MAX_STAR_EXPORTS]; -const StarExportBinding* STAR_EXPORT_STACK_END = &starExportStack_[MAX_STAR_EXPORTS - 1]; +StarExportBinding starExportBinding_; +StarExportBinding* firstStarExport; +StarExportBinding* lastStarExport; void (*addExport)(const uint16_t*, const uint16_t*); void (*addReexport)(const uint16_t*, const uint16_t*); @@ -88,7 +86,8 @@ uint32_t parseCJS (uint16_t* _source, uint32_t _sourceLen, void (*_addExport)(co error = 0; templateStack = &templateStack_[0]; openTokenPosStack = &openTokenPosStack_[0]; - starExportStack = &starExportStack_[0]; + firstStarExport = NULL; + lastStarExport = NULL; nextBraceIsClass = false; pos = (uint16_t*)(source - 1); @@ -288,11 +287,8 @@ void tryBacktrackAddStarExportBinding (uint16_t* bPos) { bPos -= charCodeByteLen(charCode); } if (identifierStart && *bPos == ' ') { - // gracefully overflow if there are too many star export bindings to track - if (starExportStack == STAR_EXPORT_STACK_END) - return; - starExportStack->id_start = bPos + 1; - starExportStack->id_end = id_end + 1; + starExportBinding_.id_start = bPos + 1; + starExportBinding_.id_end = id_end + 1; while (*bPos == ' ' && bPos > source) bPos--; switch (*bPos) { @@ -306,7 +302,15 @@ void tryBacktrackAddStarExportBinding (uint16_t* bPos) { break; default: return; } - starExportStack++; + StarExportBinding* binding = (StarExportBinding*)analysis_head; + analysis_head += sizeof(StarExportBinding); + *binding = starExportBinding_; + binding->next = NULL; + if (lastStarExport) + lastStarExport->next = binding; + else + firstStarExport = binding; + lastStarExport = binding; } } } @@ -848,15 +852,15 @@ void tryParseObjectDefineOrKeys (bool keys) { // this is done last because it is a linear search through all require bindings // and by doing this search last after verifying the structure match, // we don't have to worry about the slow search algorithm - StarExportBinding* curCheckBinding = &starExportStack_[0]; - while (curCheckBinding != starExportStack) { - if (id_len == curCheckBinding->id_end - curCheckBinding->id_start && - 0 == memcmp(id_start, curCheckBinding->id_start, id_len * sizeof(uint16_t))) { - addReexport(curCheckBinding->specifier_start, curCheckBinding->specifier_end); + StarExportBinding* binding = firstStarExport; + while (binding) { + if (id_len == binding->id_end - binding->id_start && + 0 == memcmp(id_start, binding->id_start, id_len * sizeof(uint16_t))) { + addReexport(binding->specifier_start, binding->specifier_end); pos = revertPos; return; } - curCheckBinding++; + binding = binding->next; } return; } @@ -983,8 +987,8 @@ bool tryParseRequire (enum RequireType requireType) { addReexport(reexportStart, reexportEnd); return true; default: - starExportStack->specifier_start = reexportStart; - starExportStack->specifier_end = reexportEnd; + starExportBinding_.specifier_start = reexportStart; + starExportBinding_.specifier_end = reexportEnd; return true; } } diff --git a/test/_unit.js b/test/_unit.js index 719d922..cc6cbfc 100755 --- a/test/_unit.js +++ b/test/_unit.js @@ -17,6 +17,25 @@ async function loadParser () { } } +/** + * @param {string} identifier + */ +function createStarReexport (identifier) { + return `Object.keys(${identifier}).forEach(function (key) {` + + ` if (key === "default" || key === "__esModule") return; exports[key] = ${identifier}[key]; });`; +} + +/** + * @param {number} count + */ +function createRequireDeclarations (count) { + let source = ''; + for (let index = 0; index < count; index++) { + source += `var dep${index} = require("dep${index}");\n`; + } + return source; +} + suite('Lexer', () => { suiteSetup(async () => await loadParser()); @@ -88,6 +107,69 @@ suite('Lexer', () => { } }); + test('tracks reexports across small and large binding sets', () => { + for (const count of [1, 2, 254, 255, 256, 257, 512, 4096]) { + const source = createRequireDeclarations(count); + for (const index of [0, 254, count - 1]) { + if (index >= count) continue; + assert.deepStrictEqual(parse(source + createStarReexport(`dep${index}`)), { + exports: [], reexports: [`dep${index}`] + }); + } + assert.deepStrictEqual(parse(source + createStarReexport('absent')), { + exports: [], reexports: [] + }); + } + }); + + test('tracks each require declaration form after a large binding set', () => { + const source = createRequireDeclarations(256); + for (const keyword of ['var', 'let', 'const']) { + assert.deepStrictEqual(parse(source + `${keyword} last=require("last");` + createStarReexport('last')), { + exports: [], reexports: ['last'] + }); + } + assert.deepStrictEqual(parse(source + 'var last=_interopRequireWildcard(require("last"));' + + createStarReexport('last')), { exports: [], reexports: ['last'] }); + }); + + test('tracks minimal require declarations with odd and even source lengths', () => { + for (const padding of ['', ' ']) { + const source = padding + 'var a=require("");'.repeat(4096) + + 'var last=require("last");' + createStarReexport('last'); + assert.deepStrictEqual(parse(source), { exports: [], reexports: ['last'] }); + } + }); + + test('shares analysis space between bindings and results', () => { + let source = ''; + for (let index = 0; index < 2048; index++) { + source += 'var a=require("");exports.value=1;__exportStar(require("side"),exports);'; + } + source += 'var last=require("last");' + createStarReexport('last'); + assert.deepStrictEqual(parse(source), { exports: ['value'], reexports: ['side', 'last'] }); + }); + + test('ignores require declarations inside comments', () => { + const source = '/*' + createRequireDeclarations(257) + '*/' + createStarReexport('dep256'); + assert.deepStrictEqual(parse(source), { exports: [], reexports: [] }); + }); + + for (const outcome of ['successful', 'failed']) { + test(`resets require bindings after a ${outcome} parse`, () => { + const declarations = createRequireDeclarations(257); + const reexport = createStarReexport('dep256'); + if (outcome === 'failed') { + assert.throws(() => parse(declarations + 'const value = "open'), { name: 'Error' }); + } else { + assert.deepStrictEqual(parse(declarations + reexport), { exports: [], reexports: ['dep256'] }); + } + const sourceWithoutBindings = '/*' + 'x'.repeat(declarations.length * 2) + '*/' + reexport; + assert.deepStrictEqual(parse(sourceWithoutBindings), { exports: [], reexports: [] }); + assert.deepStrictEqual(parse('exports.recovery = 1;'), { exports: ['recovery'], reexports: [] }); + }); + } + test('automatic Wasm initialization', async () => { const lexer = await import('../dist/lexer.mjs?automatic-initialization');