mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
lib: add util.getCallSite() API
PR-URL: https://github.com/nodejs/node/pull/54380 Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Claudio Wunder <cwunder@gnome.org> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
6c6a9338cb
commit
80a989f931
65
benchmark/util/get-callsite.js
Normal file
65
benchmark/util/get-callsite.js
Normal file
@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const { getCallSite } = require('node:util');
|
||||
const assert = require('node:assert');
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
n: [1e6],
|
||||
method: ['ErrorCallSite', 'ErrorCallSiteSerialized', 'CPP'],
|
||||
});
|
||||
|
||||
function ErrorGetCallSite() {
|
||||
const originalStackFormatter = Error.prepareStackTrace;
|
||||
Error.prepareStackTrace = (_err, stack) => {
|
||||
if (stack && stack.length > 1) {
|
||||
// Remove node:util
|
||||
return stack.slice(1);
|
||||
}
|
||||
return stack;
|
||||
};
|
||||
const err = new Error();
|
||||
// With the V8 Error API, the stack is not formatted until it is accessed
|
||||
err.stack; // eslint-disable-line no-unused-expressions
|
||||
Error.prepareStackTrace = originalStackFormatter;
|
||||
return err.stack;
|
||||
}
|
||||
|
||||
function ErrorCallSiteSerialized() {
|
||||
const callsite = ErrorGetCallSite();
|
||||
const serialized = [];
|
||||
for (let i = 0; i < callsite.length; ++i) {
|
||||
serialized.push({
|
||||
functionName: callsite[i].getFunctionName(),
|
||||
scriptName: callsite[i].getFileName(),
|
||||
lineNumber: callsite[i].getLineNumber(),
|
||||
column: callsite[i].getColumnNumber(),
|
||||
});
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
function main({ n, method }) {
|
||||
let fn;
|
||||
switch (method) {
|
||||
case 'ErrorCallSite':
|
||||
fn = ErrorGetCallSite;
|
||||
break;
|
||||
case 'ErrorCallSiteSerialized':
|
||||
fn = ErrorCallSiteSerialized;
|
||||
break;
|
||||
case 'CPP':
|
||||
fn = getCallSite;
|
||||
break;
|
||||
}
|
||||
let lastStack = {};
|
||||
|
||||
bench.start();
|
||||
for (let i = 0; i < n; i++) {
|
||||
const stack = fn();
|
||||
lastStack = stack;
|
||||
}
|
||||
bench.end(n);
|
||||
// Attempt to avoid dead-code elimination
|
||||
assert.ok(lastStack);
|
||||
}
|
@ -364,6 +364,63 @@ util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 });
|
||||
// when printed to a terminal.
|
||||
```
|
||||
|
||||
## `util.getCallSite(frames)`
|
||||
|
||||
> Stability: 1.1 - Active development
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `frames` {number} Number of frames returned in the stacktrace.
|
||||
**Default:** `10`. Allowable range is between 1 and 200.
|
||||
* Returns: {Object\[]} An array of stacktrace objects
|
||||
* `functionName` {string} Returns the name of the function associated with this stack frame.
|
||||
* `scriptName` {string} Returns the name of the resource that contains the script for the
|
||||
function for this StackFrame.
|
||||
* `lineNumber` {number} Returns the number, 1-based, of the line for the associate function call.
|
||||
* `column` {number} Returns the 1-based column offset on the line for the associated function call.
|
||||
|
||||
Returns an array of stacktrace objects containing the stack of
|
||||
the caller function.
|
||||
|
||||
```js
|
||||
const util = require('node:util');
|
||||
|
||||
function exampleFunction() {
|
||||
const callSites = util.getCallSite();
|
||||
|
||||
console.log('Call Sites:');
|
||||
callSites.forEach((callSite, index) => {
|
||||
console.log(`CallSite ${index + 1}:`);
|
||||
console.log(`Function Name: ${callSite.functionName}`);
|
||||
console.log(`Script Name: ${callSite.scriptName}`);
|
||||
console.log(`Line Number: ${callSite.lineNumer}`);
|
||||
console.log(`Column Number: ${callSite.column}`);
|
||||
});
|
||||
// CallSite 1:
|
||||
// Function Name: exampleFunction
|
||||
// Script Name: /home/example.js
|
||||
// Line Number: 5
|
||||
// Column Number: 26
|
||||
|
||||
// CallSite 2:
|
||||
// Function Name: anotherFunction
|
||||
// Script Name: /home/example.js
|
||||
// Line Number: 22
|
||||
// Column Number: 3
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
// A function to simulate another stack layer
|
||||
function anotherFunction() {
|
||||
exampleFunction();
|
||||
}
|
||||
|
||||
anotherFunction();
|
||||
```
|
||||
|
||||
## `util.getSystemErrorName(err)`
|
||||
|
||||
<!-- YAML
|
||||
|
12
lib/util.js
12
lib/util.js
@ -315,6 +315,17 @@ function parseEnv(content) {
|
||||
return binding.parseEnv(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the callSite
|
||||
* @param {number} frames
|
||||
* @returns {object}
|
||||
*/
|
||||
function getCallSite(frames = 10) {
|
||||
// Using kDefaultMaxCallStackSizeToCapture as reference
|
||||
validateNumber(frames, 'frames', 1, 200);
|
||||
return binding.getCallSite(frames);
|
||||
};
|
||||
|
||||
// Keep the `exports =` so that various functions can still be monkeypatched
|
||||
module.exports = {
|
||||
_errnoException,
|
||||
@ -329,6 +340,7 @@ module.exports = {
|
||||
format,
|
||||
styleText,
|
||||
formatWithOptions,
|
||||
getCallSite,
|
||||
getSystemErrorMap,
|
||||
getSystemErrorName,
|
||||
inherits,
|
||||
|
@ -98,6 +98,7 @@
|
||||
"transferList") \
|
||||
V(clone_untransferable_str, "Found invalid value in transferList.") \
|
||||
V(code_string, "code") \
|
||||
V(column_string, "column") \
|
||||
V(commonjs_string, "commonjs") \
|
||||
V(config_string, "config") \
|
||||
V(constants_string, "constants") \
|
||||
@ -166,6 +167,7 @@
|
||||
V(fragment_string, "fragment") \
|
||||
V(frames_received_string, "framesReceived") \
|
||||
V(frames_sent_string, "framesSent") \
|
||||
V(function_name_string, "functionName") \
|
||||
V(function_string, "function") \
|
||||
V(get_string, "get") \
|
||||
V(get_data_clone_error_string, "_getDataCloneError") \
|
||||
@ -215,6 +217,7 @@
|
||||
V(kind_string, "kind") \
|
||||
V(length_string, "length") \
|
||||
V(library_string, "library") \
|
||||
V(line_number_string, "lineNumber") \
|
||||
V(loop_count, "loopCount") \
|
||||
V(mac_string, "mac") \
|
||||
V(max_buffer_string, "maxBuffer") \
|
||||
@ -305,6 +308,7 @@
|
||||
V(salt_length_string, "saltLength") \
|
||||
V(scheme_string, "scheme") \
|
||||
V(scopeid_string, "scopeid") \
|
||||
V(script_name_string, "scriptName") \
|
||||
V(serial_number_string, "serialNumber") \
|
||||
V(serial_string, "serial") \
|
||||
V(servername_string, "servername") \
|
||||
|
@ -22,6 +22,7 @@ using v8::Integer;
|
||||
using v8::Isolate;
|
||||
using v8::KeyCollectionMode;
|
||||
using v8::Local;
|
||||
using v8::LocalVector;
|
||||
using v8::Object;
|
||||
using v8::ObjectTemplate;
|
||||
using v8::ONLY_CONFIGURABLE;
|
||||
@ -254,12 +255,60 @@ static void ParseEnv(const FunctionCallbackInfo<Value>& args) {
|
||||
args.GetReturnValue().Set(dotenv.ToObject(env));
|
||||
}
|
||||
|
||||
static void GetCallSite(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Isolate* isolate = env->isolate();
|
||||
|
||||
CHECK_EQ(args.Length(), 1);
|
||||
CHECK(args[0]->IsNumber());
|
||||
const uint32_t frames = args[0].As<Uint32>()->Value();
|
||||
DCHECK(frames >= 1 && frames <= 200);
|
||||
|
||||
// +1 for disregarding node:util
|
||||
Local<StackTrace> stack = StackTrace::CurrentStackTrace(isolate, frames + 1);
|
||||
const int frame_count = stack->GetFrameCount();
|
||||
LocalVector<Value> callsite_objects(isolate);
|
||||
|
||||
// Frame 0 is node:util. It should be skipped.
|
||||
for (int i = 1; i < frame_count; ++i) {
|
||||
Local<Object> obj = Object::New(isolate);
|
||||
Local<StackFrame> stack_frame = stack->GetFrame(isolate, i);
|
||||
|
||||
Utf8Value function_name(isolate, stack_frame->GetFunctionName());
|
||||
Utf8Value script_name(isolate, stack_frame->GetScriptName());
|
||||
|
||||
obj->Set(env->context(),
|
||||
env->function_name_string(),
|
||||
String::NewFromUtf8(isolate, *function_name).ToLocalChecked())
|
||||
.Check();
|
||||
obj->Set(env->context(),
|
||||
env->script_name_string(),
|
||||
String::NewFromUtf8(isolate, *script_name).ToLocalChecked())
|
||||
.Check();
|
||||
obj->Set(env->context(),
|
||||
env->line_number_string(),
|
||||
Integer::NewFromUnsigned(isolate, stack_frame->GetLineNumber()))
|
||||
.Check();
|
||||
obj->Set(env->context(),
|
||||
env->column_string(),
|
||||
Integer::NewFromUnsigned(isolate, stack_frame->GetColumn()))
|
||||
.Check();
|
||||
|
||||
callsite_objects.push_back(obj);
|
||||
}
|
||||
|
||||
Local<Array> callsites =
|
||||
Array::New(isolate, callsite_objects.data(), callsite_objects.size());
|
||||
args.GetReturnValue().Set(callsites);
|
||||
}
|
||||
|
||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(GetPromiseDetails);
|
||||
registry->Register(GetProxyDetails);
|
||||
registry->Register(GetCallerLocation);
|
||||
registry->Register(IsArrayBufferDetached);
|
||||
registry->Register(PreviewEntries);
|
||||
registry->Register(GetCallSite);
|
||||
registry->Register(GetOwnNonIndexProperties);
|
||||
registry->Register(GetConstructorName);
|
||||
registry->Register(GetExternalValue);
|
||||
@ -365,6 +414,7 @@ void Initialize(Local<Object> target,
|
||||
SetMethodNoSideEffect(
|
||||
context, target, "getConstructorName", GetConstructorName);
|
||||
SetMethodNoSideEffect(context, target, "getExternalValue", GetExternalValue);
|
||||
SetMethodNoSideEffect(context, target, "getCallSite", GetCallSite);
|
||||
SetMethod(context, target, "sleep", Sleep);
|
||||
SetMethod(context, target, "parseEnv", ParseEnv);
|
||||
|
||||
|
4
test/fixtures/get-call-site.js
vendored
Normal file
4
test/fixtures/get-call-site.js
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
const util = require('node:util');
|
||||
const assert = require('node:assert');
|
||||
assert.ok(util.getCallSite().length > 1);
|
||||
process.stdout.write(util.getCallSite()[0].scriptName);
|
106
test/parallel/test-util-getCallSite.js
Normal file
106
test/parallel/test-util-getCallSite.js
Normal file
@ -0,0 +1,106 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
const file = fixtures.path('get-call-site.js');
|
||||
|
||||
const { getCallSite } = require('node:util');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const assert = require('node:assert');
|
||||
|
||||
{
|
||||
const callsite = getCallSite();
|
||||
assert.ok(callsite.length > 1);
|
||||
assert.match(
|
||||
callsite[0].scriptName,
|
||||
/test-util-getCallSite/,
|
||||
'node:util should be ignored',
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const callsite = getCallSite(3);
|
||||
assert.strictEqual(callsite.length, 3);
|
||||
assert.match(
|
||||
callsite[0].scriptName,
|
||||
/test-util-getCallSite/,
|
||||
'node:util should be ignored',
|
||||
);
|
||||
}
|
||||
|
||||
// Guarantee dot-left numbers are ignored
|
||||
{
|
||||
const callsite = getCallSite(3.6);
|
||||
assert.strictEqual(callsite.length, 3);
|
||||
}
|
||||
|
||||
{
|
||||
const callsite = getCallSite(3.4);
|
||||
assert.strictEqual(callsite.length, 3);
|
||||
}
|
||||
|
||||
{
|
||||
assert.throws(() => {
|
||||
// Max than kDefaultMaxCallStackSizeToCapture
|
||||
getCallSite(201);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_OUT_OF_RANGE'
|
||||
}));
|
||||
assert.throws(() => {
|
||||
getCallSite(-1);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_OUT_OF_RANGE'
|
||||
}));
|
||||
assert.throws(() => {
|
||||
getCallSite({});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const callsite = getCallSite(1);
|
||||
assert.strictEqual(callsite.length, 1);
|
||||
assert.match(
|
||||
callsite[0].scriptName,
|
||||
/test-util-getCallSite/,
|
||||
'node:util should be ignored',
|
||||
);
|
||||
}
|
||||
|
||||
// Guarantee [eval] will appear on stacktraces when using -e
|
||||
{
|
||||
const { status, stderr, stdout } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'-e',
|
||||
`const util = require('util');
|
||||
const assert = require('assert');
|
||||
assert.ok(util.getCallSite().length > 1);
|
||||
process.stdout.write(util.getCallSite()[0].scriptName);
|
||||
`,
|
||||
],
|
||||
);
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
assert.strictEqual(stdout.toString(), '[eval]');
|
||||
}
|
||||
|
||||
// Guarantee the stacktrace[0] is the filename
|
||||
{
|
||||
const { status, stderr, stdout } = spawnSync(
|
||||
process.execPath,
|
||||
[file],
|
||||
);
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
assert.strictEqual(stdout.toString(), file);
|
||||
}
|
||||
|
||||
// Error.stackTraceLimit should not influence callsite size
|
||||
{
|
||||
const originalStackTraceLimit = Error.stackTraceLimit;
|
||||
Error.stackTraceLimit = 0;
|
||||
const callsite = getCallSite();
|
||||
assert.notStrictEqual(callsite.length, 0);
|
||||
Error.stackTraceLimit = originalStackTraceLimit;
|
||||
}
|
Loading…
Reference in New Issue
Block a user