diff --git a/ChangeLog.md b/ChangeLog.md index 8e7185a0c4b52..6c30920d24701 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -33,6 +33,7 @@ See docs/process.md for more on how version tagging works. state normally stored on the stack is hidden within the runtime and does not occupy linear memory at all. The default for `DEFAULT_PTHREAD_STACK_SIZE` was also reduced from 2MB to 64KB to match. +- Improved error messages for writing custom JS libraries. (#18266) 3.1.26 - 11/17/22 ----------------- diff --git a/src/compiler.js b/src/compiler.js index a8967654ebe94..01d68976564d0 100755 --- a/src/compiler.js +++ b/src/compiler.js @@ -8,6 +8,7 @@ // LLVM => JavaScript compiler, main entry point const fs = require('fs'); +global.vm = require('vm'); global.assert = require('assert'); global.nodePath = require('path'); @@ -36,7 +37,7 @@ global.read = (filename) => { }; function load(f) { - eval.call(null, read(f)); + (0, eval)(read(f) + '//# sourceURL=' + find(f)); }; // Basic utilities @@ -92,7 +93,7 @@ try { // Compiler failed on internal compiler error! printErr('Internal compiler error in src/compiler.js!'); printErr('Please create a bug report at https://github.com/emscripten-core/emscripten/issues/'); - printErr('with a log of the build and the input files used to run. Exception message: "' + err + '" | ' + err.stack); + printErr('with a log of the build and the input files used to run. Exception message: "' + (err.stack || err)); } // Work around a node.js bug where stdout buffer is not flushed at process exit: diff --git a/src/jsifier.js b/src/jsifier.js index 7cae0abb1db05..d6ff10067cf13 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -526,7 +526,7 @@ function ${name}(${args}) { const post = processMacros(preprocess(read(postFile), postFile)); print(post); - print(processMacros(preprocess(shellParts[1], shellFile))); + print(processMacros(preprocess(shellParts[1], shellFile, shellParts[0].match(/\n/g).length))); print('\n//FORWARDED_DATA:' + JSON.stringify({ librarySymbols: librarySymbols, diff --git a/src/modules.js b/src/modules.js index 5b869f533fe43..fb2616374b7dc 100644 --- a/src/modules.js +++ b/src/modules.js @@ -212,26 +212,17 @@ global.LibraryManager = { } try { processed = processMacros(preprocess(src, filename)); - eval(processed); + vm.runInThisContext(processed, { filename: filename.replace(/\.\w+$/, '.preprocessed$&') }); } catch (e) { - const details = [e, e.lineNumber ? `line number: ${e.lineNumber}` : '']; + error(`failure to execute js library "${filename}":`); if (VERBOSE) { - details.push((e.stack || '').toString().replace('Object.', filename)); - } - if (processed) { - error(`failure to execute js library "${filename}": ${details}`); - if (VERBOSE) { + if (processed) { error(`preprocessed source (you can run a js engine on this to get a clearer error message sometimes):\n=============\n${processed}\n=============`); } else { - error('use -sVERBOSE to see more details'); - } - } else { - error(`failure to process js library "${filename}": ${details}`); - if (VERBOSE) { error(`original source:\n=============\n${src}\n=============`); - } else { - error('use -sVERBOSE to see more details'); } + } else { + error('use -sVERBOSE to see more details'); } throw e; } finally { diff --git a/src/parseTools.js b/src/parseTools.js index 29863861502b7..8aa46be89eaa0 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -8,7 +8,7 @@ * Tests live in test/other/test_parseTools.js. */ -const FOUR_GB = 4 * 1024 * 1024 * 1024; +globalThis.FOUR_GB = 4 * 1024 * 1024 * 1024; const FLOAT_TYPES = new Set(['float', 'double']); let currentlyParsedFilename = ''; @@ -34,7 +34,7 @@ function processMacros(text) { // Also handles #include x.js (similar to C #include ) // Param filenameHint can be passed as a description to identify the file that is being processed, used // to locate errors for reporting and for html files to stop expansion between . -function preprocess(text, filenameHint) { +function preprocess(text, filenameHint, lineOffset = 0) { if (EXPORT_ES6 && USE_ES6_IMPORT_META) { // `eval`, Terser and Closure don't support module syntax; to allow it, // we need to temporarily replace `import.meta` and `await import` usages @@ -63,97 +63,92 @@ function preprocess(text, filenameHint) { let emptyLine = false; try { - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; - try { - if (line[line.length - 1] === '\r') { - line = line.substr(0, line.length - 1); // Windows will have '\r' left over from splitting over '\r\n' - } - if (isHtml && line.includes(' 0); + if (!inStyle) { + const trimmed = line.trim(); + if (trimmed.startsWith('#')) { + const first = trimmed.split(' ', 1)[0]; + if (first == '#if' || first == '#ifdef' || first == '#elif') { + if (first == '#ifdef') { + warn('use of #ifdef in js library. Use #if instead.'); + } + if (first == '#elif') { const curr = showStack.pop(); - if (curr == IGNORE) { - showStack.push(SHOW); - } else { - showStack.push(IGNORE); + if (curr == SHOW || curr == IGNORE_ALL) { + // If we showed to previous block we enter the IGNORE_ALL state + // and stay there until endif is seen + showStack.push(IGNORE_ALL); + continue; } - } else if (first === '#endif') { - assert(showStack.length > 0); - showStack.pop(); - } else if (first === '#warning') { - if (showCurrentLine()) { - printErr(`${filenameHint}:${i + 1}: #warning ${trimmed.substring(trimmed.indexOf(' ')).trim()}`); + } + const after = trimmed.substring(trimmed.indexOf(' ')); + const truthy = !!vm.runInThisContext(after, { filename: filenameHint, lineOffset: i, columnOffset: line.indexOf(after) }); + showStack.push(truthy ? SHOW : IGNORE); + } else if (first === '#include') { + if (showCurrentLine()) { + let filename = line.substr(line.indexOf(' ') + 1); + if (filename.startsWith('"')) { + filename = filename.substr(1, filename.length - 2); } - } else if (first === '#error') { - if (showCurrentLine()) { - error(`${filenameHint}:${i + 1}: #error ${trimmed.substring(trimmed.indexOf(' ')).trim()}`); + const included = read(filename); + const result = preprocess(included, filename); + if (result) { + ret += `// include: ${filename}\n`; + ret += result; + ret += `// end include: ${filename}\n`; } + } + } else if (first === '#else') { + assert(showStack.length > 0); + const curr = showStack.pop(); + if (curr == IGNORE) { + showStack.push(SHOW); } else { - throw new Error(`Unknown preprocessor directive on line ${i}: ``${line}```); + showStack.push(IGNORE); } - } else { + } else if (first === '#endif') { + assert(showStack.length > 0); + showStack.pop(); + } else if (first === '#warning') { if (showCurrentLine()) { - // Never emit more than one empty line at a time. - if (emptyLine && !line) { - continue; - } - ret += line + '\n'; - if (!line) { - emptyLine = true; - } else { - emptyLine = false; - } + printErr(`${filenameHint}:${i + 1}: #warning ${trimmed.substring(trimmed.indexOf(' ')).trim()}`); + } + } else if (first === '#error') { + if (showCurrentLine()) { + error(`${filenameHint}:${i + 1}: #error ${trimmed.substring(trimmed.indexOf(' ')).trim()}`); } + } else { + throw new Error(`${filenameHint}:${i + 1}: Unknown preprocessor directive ${first}`); } - } else { // !inStyle + } else { if (showCurrentLine()) { + // Never emit more than one empty line at a time. + if (emptyLine && !line) { + continue; + } ret += line + '\n'; + if (!line) { + emptyLine = true; + } else { + emptyLine = false; + } } } - } catch (e) { - printErr('parseTools.js preprocessor error in ' + filenameHint + ':' + (i + 1) + ': \"' + line + '\"!'); - throw e; + } else { // !inStyle + if (showCurrentLine()) { + ret += line + '\n'; + } } } assert(showStack.length == 0, `preprocessing error in file ${filenameHint}, \ diff --git a/tools/preprocessor.js b/tools/preprocessor.js index d94ed54f87cd8..759a8b65e9d5f 100755 --- a/tools/preprocessor.js +++ b/tools/preprocessor.js @@ -16,6 +16,7 @@ const fs = require('fs'); const path = require('path'); +global.vm = require('vm'); const arguments_ = process['argv'].slice(2); const debug = false; @@ -46,7 +47,7 @@ global.read = function(filename) { }; global.load = function(f) { - eval.call(null, read(f)); + (0, eval)(read(f) + '//# sourceURL=' + find(f)); }; const settingsFile = arguments_[0];