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
4 changes: 3 additions & 1 deletion include-wasm/cjs-module-lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down
Binary file modified lib/lexer.wasm
Binary file not shown.
44 changes: 24 additions & 20 deletions src/lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +18,6 @@ uint16_t* pos;
uint16_t* end;
uint16_t* templateStack;
uint16_t** openTokenPosStack;
StarExportBinding* starExportStack;
bool nextBraceIsClass;

uint16_t* lastReexportStart;
Expand All @@ -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*);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
}
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
}
Expand Down
82 changes: 82 additions & 0 deletions test/_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down Expand Up @@ -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');

Expand Down
Loading