lib,src: refactor assert to load error source from memory · nodejs/node@b200cd8
11'use strict';
2233const {
4- ArrayPrototypeShift,
54 Error,
65 ErrorCaptureStackTrace,
7- FunctionPrototypeBind,
8- RegExpPrototypeSymbolReplace,
9- SafeMap,
106 StringPrototypeCharCodeAt,
11- StringPrototypeIncludes,
127 StringPrototypeReplace,
13- StringPrototypeSlice,
14- StringPrototypeSplit,
15- StringPrototypeStartsWith,
168} = primordials;
17918-const { Buffer } = require('buffer');
1910const {
2011 isErrorStackTraceLimitWritable,
21- overrideStackTrace,
2212} = require('internal/errors');
2313const AssertionError = require('internal/assert/assertion_error');
24-const { openSync, closeSync, readSync } = require('fs');
25-const { EOL } = require('internal/constants');
26-const { BuiltinModule } = require('internal/bootstrap/realm');
2714const { isError } = require('internal/util');
281529-const errorCache = new SafeMap();
30-const { fileURLToPath } = require('internal/url');
31-32-let parseExpressionAt;
33-let findNodeAround;
34-let tokenizer;
35-let decoder;
16+const {
17+ getErrorSourceExpression,
18+} = require('internal/errors/error_source');
36193720// Escape control characters but not \n and \t to keep the line breaks and
3821// indentation intact.
@@ -50,111 +33,7 @@ const meta = [
50335134const escapeFn = (str) => meta[StringPrototypeCharCodeAt(str, 0)];
523553-function findColumn(fd, column, code) {
54-if (code.length > column + 100) {
55-try {
56-return parseCode(code, column);
57-} catch {
58-// End recursion in case no code could be parsed. The expression should
59-// have been found after 2500 characters, so stop trying.
60-if (code.length - column > 2500) {
61-// eslint-disable-next-line no-throw-literal
62-throw null;
63-}
64-}
65-}
66-// Read up to 2500 bytes more than necessary in columns. That way we address
67-// multi byte characters and read enough data to parse the code.
68-const bytesToRead = column - code.length + 2500;
69-const buffer = Buffer.allocUnsafe(bytesToRead);
70-const bytesRead = readSync(fd, buffer, 0, bytesToRead);
71-code += decoder.write(buffer.slice(0, bytesRead));
72-// EOF: fast path.
73-if (bytesRead < bytesToRead) {
74-return parseCode(code, column);
75-}
76-// Read potentially missing code.
77-return findColumn(fd, column, code);
78-}
79-80-function getCode(fd, line, column) {
81-let bytesRead = 0;
82-if (line === 0) {
83-// Special handle line number one. This is more efficient and simplifies the
84-// rest of the algorithm. Read more than the regular column number in bytes
85-// to prevent multiple reads in case multi byte characters are used.
86-return findColumn(fd, column, '');
87-}
88-let lines = 0;
89-// Prevent blocking the event loop by limiting the maximum amount of
90-// data that may be read.
91-let maxReads = 32; // bytesPerRead * maxReads = 512 KiB
92-const bytesPerRead = 16384;
93-// Use a single buffer up front that is reused until the call site is found.
94-let buffer = Buffer.allocUnsafe(bytesPerRead);
95-while (maxReads-- !== 0) {
96-// Only allocate a new buffer in case the needed line is found. All data
97-// before that can be discarded.
98-buffer = lines < line ? buffer : Buffer.allocUnsafe(bytesPerRead);
99-bytesRead = readSync(fd, buffer, 0, bytesPerRead);
100-// Read the buffer until the required code line is found.
101-for (let i = 0; i < bytesRead; i++) {
102-if (buffer[i] === 10 && ++lines === line) {
103-// If the end of file is reached, directly parse the code and return.
104-if (bytesRead < bytesPerRead) {
105-return parseCode(buffer.toString('utf8', i + 1, bytesRead), column);
106-}
107-// Check if the read code is sufficient or read more until the whole
108-// expression is read. Make sure multi byte characters are preserved
109-// properly by using the decoder.
110-const code = decoder.write(buffer.slice(i + 1, bytesRead));
111-return findColumn(fd, column, code);
112-}
113-}
114-}
115-}
116-117-function parseCode(code, offset) {
118-// Lazy load acorn.
119-if (parseExpressionAt === undefined) {
120-const Parser = require('internal/deps/acorn/acorn/dist/acorn').Parser;
121-({ findNodeAround } = require('internal/deps/acorn/acorn-walk/dist/walk'));
122-123-parseExpressionAt = FunctionPrototypeBind(Parser.parseExpressionAt, Parser);
124-tokenizer = FunctionPrototypeBind(Parser.tokenizer, Parser);
125-}
126-let node;
127-let start;
128-// Parse the read code until the correct expression is found.
129-for (const token of tokenizer(code, { ecmaVersion: 'latest' })) {
130-start = token.start;
131-if (start > offset) {
132-// No matching expression found. This could happen if the assert
133-// expression is bigger than the provided buffer.
134-break;
135-}
136-try {
137-node = parseExpressionAt(code, start, { ecmaVersion: 'latest' });
138-// Find the CallExpression in the tree.
139-node = findNodeAround(node, offset, 'CallExpression');
140-if (node?.node.end >= offset) {
141-return [
142-node.node.start,
143-StringPrototypeReplace(StringPrototypeSlice(code,
144-node.node.start, node.node.end),
145-escapeSequencesRegExp, escapeFn),
146-];
147-}
148-// eslint-disable-next-line no-unused-vars
149-} catch (err) {
150-continue;
151-}
152-}
153-// eslint-disable-next-line no-throw-literal
154-throw null;
155-}
156-157-function getErrMessage(message, fn) {
36+function getErrMessage(fn) {
15837const tmpLimit = Error.stackTraceLimit;
15938const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable();
16039// Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
@@ -166,85 +45,10 @@ function getErrMessage(message, fn) {
16645ErrorCaptureStackTrace(err, fn);
16746if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
16847169-overrideStackTrace.set(err, (_, stack) => stack);
170-const call = err.stack[0];
171-172-let filename = call.getFileName();
173-const line = call.getLineNumber() - 1;
174-let column = call.getColumnNumber() - 1;
175-let identifier;
176-177-if (filename) {
178-identifier = `${filename}${line}${column}`;
179-180-// Skip Node.js modules!
181-if (StringPrototypeStartsWith(filename, 'node:') &&
182-BuiltinModule.exists(StringPrototypeSlice(filename, 5))) {
183-errorCache.set(identifier, undefined);
184-return;
185-}
186-} else {
187-return message;
188-}
189-190-if (errorCache.has(identifier)) {
191-return errorCache.get(identifier);
192-}
193-194-let fd;
195-try {
196-// Set the stack trace limit to zero. This makes sure unexpected token
197-// errors are handled faster.
198-if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 0;
199-200-if (decoder === undefined) {
201-const { StringDecoder } = require('string_decoder');
202-decoder = new StringDecoder('utf8');
203-}
204-205-// ESM file prop is a file proto. Convert that to path.
206-// This ensure opensync will not throw ENOENT for ESM files.
207-const fileProtoPrefix = 'file://';
208-if (StringPrototypeStartsWith(filename, fileProtoPrefix)) {
209-filename = fileURLToPath(filename);
210-}
211-212-fd = openSync(filename, 'r', 0o666);
213-// Reset column and message.
214-({ 0: column, 1: message } = getCode(fd, line, column));
215-// Flush unfinished multi byte characters.
216-decoder.end();
217-218-// Always normalize indentation, otherwise the message could look weird.
219-if (StringPrototypeIncludes(message, '\n')) {
220-if (EOL === '\r\n') {
221-message = RegExpPrototypeSymbolReplace(/\r\n/g, message, '\n');
222-}
223-const frames = StringPrototypeSplit(message, '\n');
224-message = ArrayPrototypeShift(frames);
225-for (let i = 0; i < frames.length; i++) {
226-const frame = frames[i];
227-let pos = 0;
228-while (pos < column && (frame[pos] === ' ' || frame[pos] === '\t')) {
229-pos++;
230-}
231-message += `\n ${StringPrototypeSlice(frame, pos)}`;
232-}
233-}
234-message = `The expression evaluated to a falsy value:\n\n ${message}\n`;
235-// Make sure to always set the cache! No matter if the message is
236-// undefined or not
237-errorCache.set(identifier, message);
238-239-return message;
240-} catch {
241-// Invalidate cache to prevent trying to read this part again.
242-errorCache.set(identifier, undefined);
243-} finally {
244-// Reset limit.
245-if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
246-if (fd !== undefined)
247-closeSync(fd);
48+let source = getErrorSourceExpression(err);
49+if (source) {
50+source = StringPrototypeReplace(source, escapeSequencesRegExp, escapeFn);
51+return `The expression evaluated to a falsy value:\n\n ${source}\n`;
24852}
24953}
25054@@ -257,7 +61,7 @@ function innerOk(fn, argLen, value, message) {
25761message = 'No value argument passed to `assert.ok()`';
25862} else if (message == null) {
25963generatedMessage = true;
260-message = getErrMessage(message, fn);
64+message = getErrMessage(fn);
26165} else if (isError(message)) {
26266throw message;
26367}