Skip to content

Commit bfbe99d

Browse files
bizob2828simon-id
andauthored
Add support for argument mutation in complex argument functions (#19)
Co-authored-by: simon-id <[email protected]>
1 parent d67053d commit bfbe99d

File tree

8 files changed

+127
-12
lines changed

8 files changed

+127
-12
lines changed

src/instrumentation.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use swc_core::ecma::{
99
ast::{
1010
ArrowExpr, AssignExpr, AssignTarget, BlockStmt, ClassDecl, ClassExpr, ClassMethod,
1111
Constructor, Expr, FnDecl, FnExpr, Ident, Lit, MemberProp, MethodProp, Module, ModuleItem,
12-
Pat, PropName, Script, SimpleAssignTarget, Stmt, Str, VarDecl,
12+
Param, Pat, PropName, Script, SimpleAssignTarget, Stmt, Str, VarDecl,
1313
},
1414
atoms::Atom,
1515
};
@@ -60,9 +60,9 @@ impl Instrumentation {
6060
self.has_injected = false;
6161
}
6262

63-
fn new_fn(&self, body: BlockStmt) -> ArrowExpr {
63+
fn new_fn(&self, body: BlockStmt, params: Vec<Pat>) -> ArrowExpr {
6464
ArrowExpr {
65-
params: vec![],
65+
params,
6666
body: Box::new(body.into()),
6767
is_async: self.config.function_query.kind().is_async(),
6868
is_generator: false,
@@ -92,7 +92,7 @@ impl Instrumentation {
9292
define_channel
9393
}
9494

95-
fn insert_tracing(&mut self, body: &mut BlockStmt) {
95+
fn insert_tracing(&mut self, body: &mut BlockStmt, params: &[Param]) {
9696
self.count += 1;
9797

9898
let original_stmts = std::mem::take(&mut body.stmts);
@@ -104,7 +104,20 @@ impl Instrumentation {
104104
..body.clone()
105105
};
106106

107-
let traced_fn = self.new_fn(original_body);
107+
let original_params: Vec<Pat> = params.iter().map(|p| p.pat.clone()).collect();
108+
109+
let wrapped_fn = self.new_fn(original_body, original_params);
110+
111+
let traced_body = BlockStmt {
112+
span: Span::default(),
113+
ctxt: SyntaxContext::empty(),
114+
stmts: vec![
115+
quote!("const __apm$wrapped = $wrapped;" as Stmt, wrapped: Expr = wrapped_fn.into()),
116+
quote!("return __apm$wrapped.apply(null, __apm$original_args);" as Stmt),
117+
],
118+
};
119+
120+
let traced_fn = self.new_fn(traced_body, vec![]);
108121

109122
let id_name = self.config.get_identifier_name();
110123
let ch_ident = ident!(format!("tr_ch_apm${}", &id_name));
@@ -115,6 +128,7 @@ impl Instrumentation {
115128
));
116129

117130
body.stmts = vec![
131+
quote!("const __apm$original_args = arguments" as Stmt),
118132
quote!("const __apm$traced = $traced;" as Stmt, traced: Expr = traced_fn.into()),
119133
quote!(
120134
"if (!$ch.hasSubscribers) return __apm$traced();" as Stmt,
@@ -185,7 +199,7 @@ impl Instrumentation {
185199
&& func_expr.function.body.is_some()
186200
{
187201
if let Some(body) = func_expr.function.body.as_mut() {
188-
self.insert_tracing(body);
202+
self.insert_tracing(body, &func_expr.function.params);
189203
}
190204
true
191205
} else {
@@ -223,7 +237,7 @@ impl Instrumentation {
223237
&& node.function.body.is_some()
224238
{
225239
if let Some(body) = node.function.body.as_mut() {
226-
self.insert_tracing(body);
240+
self.insert_tracing(body, &node.function.params);
227241
}
228242
}
229243
true
@@ -279,7 +293,7 @@ impl Instrumentation {
279293
&& node.function.body.is_some()
280294
{
281295
if let Some(body) = node.function.body.as_mut() {
282-
self.insert_tracing(body);
296+
self.insert_tracing(body, &node.function.params);
283297
}
284298
}
285299
true
@@ -312,7 +326,7 @@ impl Instrumentation {
312326
&& node.function.body.is_some()
313327
{
314328
if let Some(body) = node.function.body.as_mut() {
315-
self.insert_tracing(body);
329+
self.insert_tracing(body, &node.function.params);
316330
}
317331
}
318332
false

tests/arguments_mutation/mod.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2025 Datadog, Inc.
4+
**/
5+
const assert = require('assert');
6+
7+
function fetch_simple (url, cb) {
8+
assert.strictEqual(this.this, 'this');
9+
assert.strictEqual(url, 'https://example.com');
10+
assert.strictEqual(cb.length, 2);
11+
const result = cb.apply(this, ['arg1', 'arg2']);
12+
assert.strictEqual(result, 'result');
13+
return 'return';
14+
}
15+
16+
function fetch_complex ({ url, tuple: [a = 'a', b = 'b'] }, cb, optional = 'default', ...rest) {
17+
assert.strictEqual(this.this, 'this');
18+
assert.strictEqual(url, 'https://example.com');
19+
assert.strictEqual(a, 'a');
20+
assert.strictEqual(b, 'b');
21+
assert.strictEqual(cb.length, 2);
22+
assert.strictEqual(optional, 'default');
23+
assert.deepStrictEqual(rest, []);
24+
const result = cb.apply(this, ['arg1', 'arg2']);
25+
assert.strictEqual(result, 'result');
26+
return 'return';
27+
}
28+
29+
30+
module.exports = { fetch_simple, fetch_complex };

tests/arguments_mutation/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use crate::common::*;
2+
use orchestrion_js::*;
3+
4+
#[test]
5+
fn arguments_mutation() {
6+
transpile_and_test(
7+
file!(),
8+
false,
9+
Config::new(
10+
vec![
11+
InstrumentationConfig::new(
12+
"fetch_simple",
13+
test_module_matcher(),
14+
FunctionQuery::function_declaration("fetch_simple", FunctionKind::Sync),
15+
),
16+
InstrumentationConfig::new(
17+
"fetch_complex",
18+
test_module_matcher(),
19+
FunctionQuery::function_declaration("fetch_complex", FunctionKind::Sync),
20+
),
21+
],
22+
None,
23+
),
24+
);
25+
}

tests/arguments_mutation/test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2025 Datadog, Inc.
4+
**/
5+
const { fetch_simple, fetch_complex } = require('./instrumented.js');
6+
const assert = require('assert');
7+
const { tracingChannel } = require('diagnostics_channel');
8+
9+
const handler = {
10+
start (message) {
11+
const originalCb = message.arguments[1];
12+
const wrappedCb = function (a, b) {
13+
assert.strictEqual(this.this, 'this');
14+
assert.strictEqual(a, 'arg1');
15+
assert.strictEqual(b, 'arg2');
16+
arguments[1] = 'arg2_mutated';
17+
return originalCb.apply(this, arguments);
18+
}
19+
20+
message.arguments[1] = wrappedCb;
21+
}
22+
};
23+
24+
tracingChannel('orchestrion:undici:fetch_simple').subscribe(handler);
25+
tracingChannel('orchestrion:undici:fetch_complex').subscribe(handler);
26+
27+
assert.strictEqual(fetch_simple.length, 2);
28+
assert.strictEqual(fetch_complex.length, 2);
29+
30+
const cb = function (a, b) {
31+
assert.strictEqual(this.this, 'this');
32+
assert.strictEqual(a, 'arg1');
33+
assert.strictEqual(b, 'arg2_mutated');
34+
return 'result';
35+
};
36+
37+
assert.strictEqual(fetch_simple.apply({ this: 'this' }, ['https://example.com', cb]), 'return');
38+
assert.strictEqual(fetch_complex.apply({ this: 'this' }, [{ url: 'https://example.com', tuple: [] }, cb]), 'return');

tests/instrumentor_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
**/
55
mod common;
66

7+
mod arguments_mutation;
78
mod class_expression_cjs;
89
mod class_method_cjs;
910
mod constructor_cjs;

tests/wasm/testdata/expected-cjs.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ module.exports = class Up {
2828
}
2929
}
3030
fetch() {
31+
const __apm$original_args = arguments;
3132
const __apm$traced = ()=>{
32-
console.log('fetch');
33+
const __apm$wrapped = ()=>{
34+
console.log('fetch');
35+
};
36+
return __apm$wrapped.apply(null, __apm$original_args);
3337
};
3438
if (!tr_ch_apm$up_fetch.hasSubscribers) return __apm$traced();
3539
return tr_ch_apm$up_fetch.traceSync(__apm$traced, {

tests/wasm/testdata/expected.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ export class Up {
2828
}
2929
}
3030
fetch() {
31+
const __apm$original_args = arguments;
3132
const __apm$traced = ()=>{
32-
console.log('fetch');
33+
const __apm$wrapped = ()=>{
34+
console.log('fetch');
35+
};
36+
return __apm$wrapped.apply(null, __apm$original_args);
3337
};
3438
if (!tr_ch_apm$up_fetch.hasSubscribers) return __apm$traced();
3539
return tr_ch_apm$up_fetch.traceSync(__apm$traced, {

tests/wasm/tests.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ assert.strictEqual(output, expected.toString('utf8'));
3737
const originalCjs = await fs.readFile(path.join(import.meta.dirname, './testdata/original-cjs.js'))
3838
const outputCjs = matchedTransforms.transform(originalCjs.toString('utf8'), 'cjs');
3939

40-
4140
const expectedCjs = await fs.readFile(path.join(import.meta.dirname, './testdata/expected-cjs.js'))
4241
assert.strictEqual(outputCjs, expectedCjs.toString('utf8'));
4342

0 commit comments

Comments
 (0)