Skip to content

Conversation

@vivek958
Copy link

@vivek958 vivek958 commented Oct 25, 2025

Description

Fixes #60401

The loadCJSModule function in lib/internal/modules/esm/translators.js was not validating source content before passing it to the native compileFunctionForCJSLoader function. When source was null or undefined, this caused an internal assertion failure instead of throwing a meaningful error.

Changes

  • Added source validation in loadCJSModule to check for null/undefined/empty source
  • Throws ERR_INVALID_RETURN_PROPERTY_VALUE with descriptive message instead of internal assertion

Testing

The validation prevents the internal assertion that was occurring with custom loaders returning null source.

  • Added test file: test/parallel/test-esm-loader-null-source.js
  • Tests verify proper error handling for null/undefined/empty source
  • Tests ensure ERR_INTERNAL_ASSERTION is not thrown

Checklist

  • make -j4 test (UNIX) or vcbuild test (Windows) passes (will be verified by CI)
  • commit message follows commit guidelines

…_ASSERTION in CJS module loading

Fixes: nodejs#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.
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/loaders

@nodejs-github-bot nodejs-github-bot added esm Issues and PRs related to the ECMAScript Modules implementation. needs-ci PRs that need a full CI run. labels Oct 25, 2025
@aduh95
Copy link
Contributor

aduh95 commented Oct 25, 2025

The validation prevents the internal assertion that was occurring with custom loaders returning null source. Not yet tested due to my system limitations.

Could you add a test?

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: nodejs#60401
@vivek958
Copy link
Author

vivek958 commented Oct 25, 2025

Could you add a test?

done, added test case in second commit to verify the fix works correctly. The test covers null, undefined, and empty string source values to ensure ERR_INVALID_RETURN_PROPERTY_VALUE is thrown instead of ERR_INTERNAL_ASSERTION. @aduh95

@vivek958
Copy link
Author

Friendly ping @nodejs/loaders - would appreciate any feedback when you have time!

Comment on lines +34 to +50
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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' });

Comment on lines +15 to +32
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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's declare the function outside the string so it's easier to read and can be linted

Suggested change
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));
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,
[
'--no-warnings',
'--input-type=module',
'--eval',
`
import { register } from 'node:module';
// Register a custom loader that returns null source
register('data:text/javascript,export ' + encodeURIComponent(${load}));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

esm Issues and PRs related to the ECMAScript Modules implementation. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ERR_INTERNAL_ASSERTION

3 participants