Skip to content

Commit 7c14449

Browse files
committed
Support function assignments for instrumentation
1 parent f23ec9e commit 7c14449

File tree

10 files changed

+444
-50
lines changed

10 files changed

+444
-50
lines changed

instrumentation-wasm/src/js_transformer/helpers/get_method_arg_names.rs renamed to instrumentation-wasm/src/js_transformer/helpers/get_arg_names.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use oxc_ast::ast::MethodDefinition;
1+
use oxc_ast::ast::{Function, MethodDefinition};
22

33
pub fn get_method_arg_names(method_definition: &MethodDefinition) -> Vec<String> {
44
let mut arg_names = Vec::new();
@@ -14,3 +14,13 @@ pub fn get_method_arg_names(method_definition: &MethodDefinition) -> Vec<String>
1414

1515
arg_names
1616
}
17+
18+
pub fn get_function_arg_names(method_definition: &Function) -> Vec<String> {
19+
let mut arg_names = Vec::new();
20+
21+
method_definition.params.items.iter().for_each(|param| {
22+
arg_names.push(param.pattern.get_identifier_name().unwrap().to_string());
23+
});
24+
25+
arg_names
26+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use oxc_allocator::Allocator;
2+
use oxc_ast::ast::{Expression, MemberExpression};
3+
4+
pub fn get_name_str_for_member_expr<'a>(
5+
allocator: &'a Allocator,
6+
member_expr: &MemberExpression,
7+
) -> Option<&'a str> {
8+
let mut obj_name_str: &str = "";
9+
let mut prop_name_str: &str = "";
10+
11+
match member_expr {
12+
MemberExpression::StaticMemberExpression(static_member_expr) => {
13+
prop_name_str = static_member_expr.property.name.as_str()
14+
}
15+
MemberExpression::ComputedMemberExpression(computed_member_expr) => {
16+
match &computed_member_expr.expression {
17+
Expression::Identifier(identifier_ref) => {
18+
prop_name_str = identifier_ref.name.as_str();
19+
}
20+
_ => {
21+
// Unsupported AST type
22+
return None;
23+
}
24+
}
25+
}
26+
_ => {
27+
// Unsupported AST type
28+
return None;
29+
}
30+
}
31+
32+
match member_expr.object() {
33+
Expression::Identifier(identifier_ref) => {
34+
obj_name_str = identifier_ref.name.as_str();
35+
}
36+
_ => {
37+
// Unsupported AST type
38+
return None;
39+
}
40+
}
41+
42+
if obj_name_str.is_empty() || prop_name_str.is_empty() {
43+
return None;
44+
}
45+
46+
if member_expr.is_computed() {
47+
return Some(allocator.alloc_str(&format!("{}[{}]", obj_name_str, prop_name_str)));
48+
}
49+
50+
Some(&allocator.alloc_str(&format!("{}.{}", obj_name_str, prop_name_str)))
51+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use super::parse_js_code_to_statements::parse_js_code_to_statements;
2+
use oxc_allocator::{Allocator, Box};
3+
use oxc_ast::ast::FunctionBody;
4+
use oxc_span::SourceType;
5+
6+
pub fn insert_single_statement_into_func<'a>(
7+
allocator: &'a Allocator,
8+
body: &mut Box<'a, FunctionBody<'a>>,
9+
pos: usize,
10+
source_text: &'a str,
11+
) {
12+
body.statements.insert(
13+
pos,
14+
parse_js_code_to_statements(&allocator, &source_text, SourceType::mjs())
15+
.into_iter()
16+
.next()
17+
.unwrap(),
18+
);
19+
}
20+
21+
// Add a statement to the beginning of the function: __instrumentInspectArgs('function_identifier', arguments);
22+
pub fn insert_inspect_args<'a>(
23+
allocator: &'a Allocator,
24+
identifier: &str,
25+
pkg_name: &'a str,
26+
pkg_version: &'a str,
27+
instruction_name: &str,
28+
body: &mut Box<'a, FunctionBody<'a>>,
29+
) {
30+
let source_text: &'a str = allocator.alloc_str(&format!(
31+
"__instrumentInspectArgs('{}', arguments, '{}', '{}', '{}', this);",
32+
identifier, pkg_name, pkg_version, instruction_name
33+
));
34+
35+
insert_single_statement_into_func(allocator, body, 0, source_text);
36+
}
37+
38+
// Modify the arguments by adding a statement to the beginning of the function
39+
// [arg1, arg2, ...] = __instrumentModifyArgs('function_identifier', [arg1, arg2, ...]);
40+
pub fn insert_modify_args<'a>(
41+
allocator: &'a Allocator,
42+
identifier: &str,
43+
arg_names_str: &str,
44+
body: &mut Box<'a, FunctionBody<'a>>,
45+
) {
46+
let source_text: &'a str = allocator.alloc_str(&format!(
47+
"[{}] = __instrumentModifyArgs('{}', [{}]);",
48+
arg_names_str, identifier, arg_names_str
49+
));
50+
51+
insert_single_statement_into_func(allocator, body, 0, source_text);
52+
}

instrumentation-wasm/src/js_transformer/helpers/insert_single_statement_into_func.rs

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
pub mod get_arg_names;
12
pub mod get_import_code_str;
2-
pub mod get_method_arg_names;
3-
pub mod insert_single_statement_into_func;
3+
pub mod get_name_str_for_member_expr;
4+
pub mod insert_code;
45
pub mod parse_js_code_to_statements;
56
pub mod select_sourcetype_based_on_enum;

instrumentation-wasm/src/js_transformer/helpers/parse_js_code_to_statements.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub fn parse_js_code_to_statements<'a>(
1010
) -> Vec<'a, Statement<'a>> {
1111
let parser_result = Parser::new(&allocator, source_text, source_type)
1212
.with_options(ParseOptions {
13-
allow_return_outside_function: true,
13+
allow_return_outside_function: true, // Could be partical code string that should be parsed
1414
..ParseOptions::default()
1515
})
1616
.parse();

instrumentation-wasm/src/js_transformer/transformer.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,7 @@ pub fn transform_code_str(
2929

3030
let source_type = select_sourcetype_based_on_enum(src_type);
3131

32-
let parser_result = Parser::new(&allocator, &code, source_type)
33-
.with_options(ParseOptions {
34-
allow_return_outside_function: true, // Maybe some run
35-
..ParseOptions::default()
36-
})
37-
.parse();
32+
let parser_result = Parser::new(&allocator, &code, source_type).parse();
3833

3934
if parser_result.panicked || parser_result.errors.len() > 0 {
4035
return format!("#ERR: {:?}", parser_result.errors);

instrumentation-wasm/src/js_transformer/transformer_impl.rs

Lines changed: 97 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
use super::helpers::get_method_arg_names;
2-
use super::instructions::FileInstructions;
31
use oxc_allocator::Allocator;
4-
use oxc_ast::ast::MethodDefinition;
2+
use oxc_ast::ast::{AssignmentOperator, Expression, MethodDefinition};
53
use oxc_traverse::{Traverse, TraverseCtx};
64

7-
use super::helpers::insert_single_statement_into_func::insert_single_statement_into_func;
5+
use super::helpers::{
6+
get_arg_names::get_function_arg_names,
7+
get_arg_names::get_method_arg_names,
8+
get_name_str_for_member_expr::get_name_str_for_member_expr,
9+
insert_code::{insert_inspect_args, insert_modify_args},
10+
};
11+
use super::instructions::FileInstructions;
812

913
pub struct Transformer<'a> {
1014
pub allocator: &'a Allocator,
@@ -44,34 +48,107 @@ impl<'a> Traverse<'a> for Transformer<'a> {
4448

4549
// We need to collect the arg names before we make the body mutable
4650
let arg_names = if instruction.modify_args {
47-
get_method_arg_names::get_method_arg_names(node)
51+
get_method_arg_names(node)
4852
} else {
4953
Vec::new()
5054
};
5155

5256
let body = node.value.body.as_mut().unwrap();
5357

5458
if instruction.modify_args && !arg_names.is_empty() {
55-
// Modify the arguments by adding a statement to the beginning of the function
56-
// [arg1, arg2, ...] = __instrumentModifyArgs('function_identifier', [arg1, arg2, ...]);
57-
5859
let arg_names_str = arg_names.join(", ");
59-
let source_text: &'a str = self.allocator.alloc_str(&format!(
60-
"[{}] = __instrumentModifyArgs('{}', [{}]);",
61-
arg_names_str, instruction.identifier, arg_names_str
62-
));
63-
64-
insert_single_statement_into_func(self.allocator, body, 0, &source_text);
60+
insert_modify_args(
61+
self.allocator,
62+
&instruction.identifier,
63+
&arg_names_str,
64+
body,
65+
);
6566
}
6667

6768
if instruction.inspect_args {
68-
// Add a statement to the beginning of the function: __instrumentInspectArgs('function_identifier', arguments);
69-
let source_text: &'a str = self.allocator.alloc_str(&format!(
70-
"__instrumentInspectArgs('{}', arguments, '{}', '{}', '{}', this);",
71-
instruction.identifier, self.pkg_name, self.pkg_version, instruction.name
72-
));
69+
insert_inspect_args(
70+
self.allocator,
71+
&instruction.identifier,
72+
self.pkg_name,
73+
self.pkg_version,
74+
&instruction.name,
75+
body,
76+
);
77+
}
78+
79+
// Todo support return value modification
80+
}
81+
82+
fn enter_assignment_expression(
83+
&mut self,
84+
node: &mut oxc_ast::ast::AssignmentExpression<'a>,
85+
_ctx: &mut TraverseCtx<'a>,
86+
) {
87+
if node.operator != AssignmentOperator::Assign {
88+
// Return if operator is not =
89+
return;
90+
}
91+
92+
if !node.left.is_member_expression() || !node.right.is_function() {
93+
return;
94+
}
7395

74-
insert_single_statement_into_func(self.allocator, body, 0, source_text);
96+
let member_expression = node.left.as_member_expression().unwrap();
97+
98+
let name_str_opt = get_name_str_for_member_expr(self.allocator, member_expression);
99+
if name_str_opt.is_none() {
100+
// Could not determine the name of the assignment
101+
return;
102+
}
103+
let name_str = name_str_opt.unwrap();
104+
105+
let matching_instruction = self
106+
.file_instructions
107+
.functions
108+
.iter()
109+
.find(|f| f.node_type == "FunctionAssignment" && f.name == name_str);
110+
111+
if matching_instruction.is_none() {
112+
// This function assignment should not be instrumented
113+
return;
114+
}
115+
116+
let instruction = matching_instruction.unwrap();
117+
118+
// We need to modify the function expression
119+
let function_expression = match &mut node.right {
120+
Expression::FunctionExpression(func_expr) => func_expr,
121+
_ => return,
122+
};
123+
124+
// We need to collect the arg names before we make the body mutable
125+
let arg_names = if instruction.modify_args {
126+
get_function_arg_names(function_expression)
127+
} else {
128+
Vec::new()
129+
};
130+
131+
let body = function_expression.body.as_mut().unwrap();
132+
133+
if instruction.modify_args && !arg_names.is_empty() {
134+
let arg_names_str = arg_names.join(", ");
135+
insert_modify_args(
136+
self.allocator,
137+
&instruction.identifier,
138+
&arg_names_str,
139+
body,
140+
);
141+
}
142+
143+
if instruction.inspect_args {
144+
insert_inspect_args(
145+
self.allocator,
146+
&instruction.identifier,
147+
self.pkg_name,
148+
self.pkg_version,
149+
&instruction.name,
150+
body,
151+
);
75152
}
76153

77154
// Todo support return value modification

0 commit comments

Comments
 (0)