From ea08d7f187de068126e319d1e70648def085720f Mon Sep 17 00:00:00 2001 From: Vivek Pandey <98891756+vivek958@users.noreply.github.com> Date: Sat, 25 Oct 2025 20:39:55 +0530 Subject: [PATCH 1/7] Validate source before compiling CJS module. module: fix ERR_INTERNAL_ASSERTION in CJS module loading Fixes: https://github.com/nodejs/node/issues/60401 The loadCJSModule function was not properly validating source content before passing it to compileFunctionForCJSLoader, causing internal assertions when source was null or undefined. This change adds proper source validation and throws a meaningful ERR_INVALID_RETURN_PROPERTY_VALUE error instead of failing with an internal assertion. --- lib/internal/modules/esm/translators.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 446349113e13bc..a97d4116c4c3fc 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -124,8 +124,21 @@ translators.set('module', function moduleStrategy(url, translateContext, parentU * @param {boolean} isMain - Whether the module is the entrypoint */ function loadCJSModule(module, source, url, filename, isMain) { - const compileResult = compileFunctionForCJSLoader(source, filename, false /* is_sea_main */, false); + // Validate source before compilation to prevent internal assertion errors. + // Without this check, null or undefined source causes ERR_INTERNAL_ASSERTION + // when passed to compileFunctionForCJSLoader. + // Refs: https://github.com/nodejs/node/issues/60401 + if (source == null || source === '') { + throw new ERR_INVALID_RETURN_PROPERTY_VALUE( + 'non-empty string', + 'load', + 'source', + source, + ); + } + const compileResult = compileFunctionForCJSLoader(source, filename, false /* is_sea_main */, false); + const { function: compiledWrapper, sourceMapURL, sourceURL } = compileResult; // Cache the source map for the cjs module if present. if (sourceMapURL) { From 8ff9fbe159b673304d1a9a84b6349064b3ecdcbf Mon Sep 17 00:00:00 2001 From: Vivek Pandey <98891756+vivek958@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:10:06 +0530 Subject: [PATCH 2/7] test: add test for null source in ESM loader Add test cases to verify that loadCJSModule properly handles null, undefined, and empty string source values by throwing ERR_INVALID_RETURN_PROPERTY_VALUE instead of triggering ERR_INTERNAL_ASSERTION. Tests three scenarios: - Custom loader returning null source - Custom loader returning undefined source - Custom loader returning empty string source Refs: https://github.com/nodejs/node/issues/60401 --- test/parallel/test-esm-loader-null-source.js | 181 +++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 test/parallel/test-esm-loader-null-source.js diff --git a/test/parallel/test-esm-loader-null-source.js b/test/parallel/test-esm-loader-null-source.js new file mode 100644 index 00000000000000..788e38e5003666 --- /dev/null +++ b/test/parallel/test-esm-loader-null-source.js @@ -0,0 +1,181 @@ +'use strict'; + +// Test that ESM loader handles null/undefined source gracefully +// and throws meaningful error instead of ERR_INTERNAL_ASSERTION. +// Refs: https://github.com/nodejs/node/issues/60401 + +const common = require('../common'); +const assert = require('assert'); +const { spawnSync } = require('child_process'); +const fixtures = require('../common/fixtures'); + +// Test case: Loader returning null source for CommonJS module +// This should throw ERR_INVALID_RETURN_PROPERTY_VALUE, not ERR_INTERNAL_ASSERTION +{ + const result = spawnSync( + process.execPath, + [ + '--no-warnings', + '--input-type=module', + '--eval', + ` + import { register } from 'node:module'; + + // Register a custom loader that returns null source + const code = 'export function load(url, context, next) {' + + ' if (url.includes("test-null-source")) {' + + ' return { format: "commonjs", source: null, shortCircuit: true };' + + ' }' + + ' return next(url);' + + '}'; + + register('data:text/javascript,' + encodeURIComponent(code)); + + try { + await import('file:///test-null-source.js'); + console.log('ERROR: Should have thrown'); + process.exit(1); + } catch (err) { + // Should throw ERR_INVALID_RETURN_PROPERTY_VALUE, not ERR_INTERNAL_ASSERTION + if (err.code === 'ERR_INTERNAL_ASSERTION') { + console.log('FAIL: Got ERR_INTERNAL_ASSERTION'); + process.exit(1); + } + if (err.code === 'ERR_INVALID_RETURN_PROPERTY_VALUE') { + console.log('PASS: Got expected error'); + process.exit(0); + } + console.log('ERROR: Got unexpected error:', err.code); + process.exit(1); + } + `, + ], + { encoding: 'utf8' } + ); + + const output = result.stdout + result.stderr; + + // Verify test passed + assert.ok( + output.includes('PASS: Got expected error'), + 'Should pass with expected error. Output: ' + output + ); + + assert.strictEqual( + result.status, + 0, + 'Process should exit with code 0. Output: ' + output + ); +} + +// Test case: Loader returning undefined source +{ + const result = spawnSync( + process.execPath, + [ + '--no-warnings', + '--input-type=module', + '--eval', + ` + import { register } from 'node:module'; + + const code = 'export function load(url, context, next) {' + + ' if (url.includes("test-undefined-source")) {' + + ' return { format: "commonjs", source: undefined, shortCircuit: true };' + + ' }' + + ' return next(url);' + + '}'; + + register('data:text/javascript,' + encodeURIComponent(code)); + + try { + await import('file:///test-undefined-source.js'); + console.log('ERROR: Should have thrown'); + process.exit(1); + } catch (err) { + if (err.code === 'ERR_INTERNAL_ASSERTION') { + console.log('FAIL: Got ERR_INTERNAL_ASSERTION'); + process.exit(1); + } + if (err.code === 'ERR_INVALID_RETURN_PROPERTY_VALUE') { + console.log('PASS: Got expected error'); + process.exit(0); + } + console.log('ERROR: Got unexpected error:', err.code); + process.exit(1); + } + `, + ], + { encoding: 'utf8' } + ); + + const output = result.stdout + result.stderr; + + assert.ok( + output.includes('PASS: Got expected error'), + 'Should pass with expected error for undefined. Output: ' + output + ); + + assert.strictEqual( + result.status, + 0, + 'Process should exit with code 0. Output: ' + output + ); +} + +// Test case: Loader returning empty string source +{ + const result = spawnSync( + process.execPath, + [ + '--no-warnings', + '--input-type=module', + '--eval', + ` + import { register } from 'node:module'; + + const code = 'export function load(url, context, next) {' + + ' if (url.includes("test-empty-source")) {' + + ' return { format: "commonjs", source: "", shortCircuit: true };' + + ' }' + + ' return next(url);' + + '}'; + + register('data:text/javascript,' + encodeURIComponent(code)); + + try { + await import('file:///test-empty-source.js'); + console.log('ERROR: Should have thrown'); + process.exit(1); + } catch (err) { + if (err.code === 'ERR_INTERNAL_ASSERTION') { + console.log('FAIL: Got ERR_INTERNAL_ASSERTION'); + process.exit(1); + } + if (err.code === 'ERR_INVALID_RETURN_PROPERTY_VALUE') { + console.log('PASS: Got expected error'); + process.exit(0); + } + console.log('ERROR: Got unexpected error:', err.code); + process.exit(1); + } + `, + ], + { encoding: 'utf8' } + ); + + const output = result.stdout + result.stderr; + + assert.ok( + output.includes('PASS: Got expected error'), + 'Should pass with expected error for empty string. Output: ' + output + ); + + assert.strictEqual( + result.status, + 0, + 'Process should exit with code 0. Output: ' + output + ); +} + +console.log('All tests passed!'); From 883566390930dbedf7cffb8235aa5fcea08788b3 Mon Sep 17 00:00:00 2001 From: Vivek Pandey <98891756+vivek958@users.noreply.github.com> Date: Thu, 30 Oct 2025 01:15:33 +0530 Subject: [PATCH 3/7] Update test/parallel/test-esm-loader-null-source.js Co-authored-by: Antoine du Hamel --- test/parallel/test-esm-loader-null-source.js | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/test/parallel/test-esm-loader-null-source.js b/test/parallel/test-esm-loader-null-source.js index 788e38e5003666..6eab317554c566 100644 --- a/test/parallel/test-esm-loader-null-source.js +++ b/test/parallel/test-esm-loader-null-source.js @@ -31,23 +31,7 @@ const fixtures = require('../common/fixtures'); register('data:text/javascript,' + encodeURIComponent(code)); - try { - await import('file:///test-null-source.js'); - console.log('ERROR: Should have thrown'); - process.exit(1); - } catch (err) { - // Should throw ERR_INVALID_RETURN_PROPERTY_VALUE, not ERR_INTERNAL_ASSERTION - if (err.code === 'ERR_INTERNAL_ASSERTION') { - console.log('FAIL: Got ERR_INTERNAL_ASSERTION'); - process.exit(1); - } - if (err.code === 'ERR_INVALID_RETURN_PROPERTY_VALUE') { - console.log('PASS: Got expected error'); - process.exit(0); - } - console.log('ERROR: Got unexpected error:', err.code); - process.exit(1); - } + await assert.rejects(import('file:///test-null-source.js'), { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }); `, ], { encoding: 'utf8' } From 4d2299a15d25f2ceff631442105e625bc24aadd5 Mon Sep 17 00:00:00 2001 From: Vivek Pandey <98891756+vivek958@users.noreply.github.com> Date: Thu, 30 Oct 2025 01:15:55 +0530 Subject: [PATCH 4/7] Update test/parallel/test-esm-loader-null-source.js Co-authored-by: Antoine du Hamel --- test/parallel/test-esm-loader-null-source.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/parallel/test-esm-loader-null-source.js b/test/parallel/test-esm-loader-null-source.js index 6eab317554c566..5f490568c00fc7 100644 --- a/test/parallel/test-esm-loader-null-source.js +++ b/test/parallel/test-esm-loader-null-source.js @@ -12,6 +12,12 @@ const fixtures = require('../common/fixtures'); // Test case: Loader returning null source for CommonJS module // This should throw ERR_INVALID_RETURN_PROPERTY_VALUE, not ERR_INTERNAL_ASSERTION { + function load(url, context, next) { + if (url.includes("test-null-source")) { + return { format: "commonjs", source: null, shortCircuit: true }; + } + return next(url); + } const result = spawnSync( process.execPath, [ @@ -22,14 +28,7 @@ const fixtures = require('../common/fixtures'); import { register } from 'node:module'; // Register a custom loader that returns null source - const code = 'export function load(url, context, next) {' + - ' if (url.includes("test-null-source")) {' + - ' return { format: "commonjs", source: null, shortCircuit: true };' + - ' }' + - ' return next(url);' + - '}'; - - register('data:text/javascript,' + encodeURIComponent(code)); + register('data:text/javascript,export ' + encodeURIComponent(${load})); await assert.rejects(import('file:///test-null-source.js'), { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }); `, From 8da8e2bef3d4fff49e3348f21beecec20e6a4db6 Mon Sep 17 00:00:00 2001 From: Vivek Pandey <98891756+vivek958@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:20:44 +0530 Subject: [PATCH 5/7] Refactor source validation to check for null and undefined --- lib/internal/modules/esm/translators.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index a97d4116c4c3fc..0df94f4b926de4 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -128,7 +128,7 @@ function loadCJSModule(module, source, url, filename, isMain) { // Without this check, null or undefined source causes ERR_INTERNAL_ASSERTION // when passed to compileFunctionForCJSLoader. // Refs: https://github.com/nodejs/node/issues/60401 - if (source == null || source === '') { + if (source === null || source === undefined) { throw new ERR_INVALID_RETURN_PROPERTY_VALUE( 'non-empty string', 'load', From 34c41a113fad02c71404bcdcde1d342d1e4894e6 Mon Sep 17 00:00:00 2001 From: Vivek Pandey <98891756+vivek958@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:41:11 +0530 Subject: [PATCH 6/7] Add empty test-null-source.js file --- test/fixtures/test-null-source.js | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/fixtures/test-null-source.js diff --git a/test/fixtures/test-null-source.js b/test/fixtures/test-null-source.js new file mode 100644 index 00000000000000..867f629c0efaf2 --- /dev/null +++ b/test/fixtures/test-null-source.js @@ -0,0 +1,2 @@ +// test/fixtures/test-null-source.js +export {}; From 0c311c49095d2844b95af11274d42800b060da87 Mon Sep 17 00:00:00 2001 From: Vivek Pandey <98891756+vivek958@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:43:49 +0530 Subject: [PATCH 7/7] Update test-esm-loader-null-source.js --- test/parallel/test-esm-loader-null-source.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-esm-loader-null-source.js b/test/parallel/test-esm-loader-null-source.js index 5f490568c00fc7..a96d35eb796f41 100644 --- a/test/parallel/test-esm-loader-null-source.js +++ b/test/parallel/test-esm-loader-null-source.js @@ -1,9 +1,11 @@ 'use strict'; +import { pathToFileURL } from 'url'; +import path from 'path'; // Test that ESM loader handles null/undefined source gracefully // and throws meaningful error instead of ERR_INTERNAL_ASSERTION. // Refs: https://github.com/nodejs/node/issues/60401 - +const fixturePath = pathToFileURL(path.join(__dirname, '../fixtures/test-null-source.js')).href; const common = require('../common'); const assert = require('assert'); const { spawnSync } = require('child_process'); @@ -127,7 +129,7 @@ const fixtures = require('../common/fixtures'); register('data:text/javascript,' + encodeURIComponent(code)); try { - await import('file:///test-empty-source.js'); + await import(fixturePath); console.log('ERROR: Should have thrown'); process.exit(1); } catch (err) {