#[derive(Debug, Clone, PartialEq)]
pub struct EscapeAnalysisDecision {
pub can_stack_allocate: bool,
pub escape_level: EscapeLevel,
pub allocation_savings: f64,
pub recommendation: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EscapeLevel {
NoEscape,
ArgEscape,
GlobalEscape,
ThreadEscape,
}
#[derive(Debug, Clone)]
pub struct ObjectInfo {
pub name: String,
pub object_type: ObjectType,
pub definition_line: usize,
pub usage_lines: Vec<usize>,
pub is_escaping: bool,
pub escape_path: EscapePath,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ObjectType {
PlainObject,
Array,
Function,
Class,
Other,
}
#[derive(Debug, Clone)]
pub struct EscapePath {
pub escapes_to_global: bool,
pub escapes_to_argument: bool,
pub escapes_to_return: bool,
pub escapes_to_property: bool,
pub escape_functions: HashSet<String>,
}
pub struct EscapeAnalysisOptimizer {
analysis_history: HashMap<String, EscapeStats>,
}
#[derive(Debug, Clone)]
struct EscapeStats {
pub total_analyzed: usize,
pub stack_allocated: usize,
pub heap_allocated: usize,
pub total_savings: f64,
pub last_analysis: Instant,
}
impl EscapeAnalysisOptimizer {
pub fn new() -> Self {
Self {
analysis_history: HashMap::new(),
}
}
pub fn analyze_escape(&self, code: &str) -> Vec<ObjectInfo> {
let lines: Vec<&str> = code.lines().collect();
let mut objects = Vec::new();
for (line_num, line) in lines.iter().enumerate() {
if let Some(obj_info) = self.extract_object_definition(line, line_num) {
objects.push(obj_info);
}
}
for obj in &mut objects {
self.analyze_escape_path(obj, &lines);
}
objects
}
pub fn make_escape_decision(&self, obj_info: &ObjectInfo) -> EscapeAnalysisDecision {
match obj_info.escape_level {
EscapeLevel::NoEscape => {
let savings: _ = self.calculate_stack_allocation_savings(obj_info);
EscapeAnalysisDecision {
can_stack_allocate: true,
escape_level: obj_info.escape_level.clone(),
allocation_savings: savings,
recommendation: format!(
"Can stack allocate '{}' - no escape detected, savings: {:.2}",
obj_info.name, savings
),
}
}
EscapeLevel::ArgEscape => {
let savings: _ = self.calculate_stack_allocation_savings(obj_info) * 0.5;
EscapeAnalysisDecision {
can_stack_allocate: true,
escape_level: obj_info.escape_level.clone(),
allocation_savings: savings,
recommendation: format!(
"Can stack allocate '{}' with risk - escapes to argument, savings: {:.2}",
obj_info.name, savings
),
}
}
EscapeLevel::GlobalEscape | EscapeLevel::ThreadEscape => {
EscapeAnalysisDecision {
can_stack_allocate: false,
escape_level: obj_info.escape_level.clone(),
allocation_savings: 0.0,
recommendation: format!(
"Must heap allocate '{}' - escapes to {:?}",
obj_info.name, obj_info.escape_level
),
}
}
}
}
fn extract_object_definition(&self, line: &str, line_num: usize) -> Option<ObjectInfo> {
let trimmed: _ = line.trim();
if trimmed.starts_with("let ") || trimmed.starts_with("const ") || trimmed.starts_with("var ") {
let after_keyword: _ = if trimmed.starts_with("let ") {
&trimmed[4..]
} else if trimmed.starts_with("const ") {
&trimmed[6..]
} else {
&trimmed[4..]
};
if let Some(eq_pos) = after_keyword.find('=') {
let var_name: _ = after_keyword[..eq_pos].trim();
let value_part: _ = &after_keyword[eq_pos + 1..].trim();
if self.is_valid_identifier(var_name) {
let obj_type: _ = self.determine_object_type(value_part);
return Some(ObjectInfo {
name: var_name.to_string(),
object_type: obj_type,
definition_line: line_num,
usage_lines: Vec::new(),
is_escaping: false,
escape_path: EscapePath {
escapes_to_global: false,
escapes_to_argument: false,
escapes_to_return: false,
escapes_to_property: false,
escape_functions: HashSet::new(),
},
});
}
}
}
if trimmed.starts_with("function ") {
if let Some(params_start) = trimmed.find('(') {
if let Some(params_end) = trimmed[params_start..].find(')') {
let params: _ = &trimmed[params_start + 1..params_start + params_end];
let param_names: Vec<&str> = params.split(',').map(|p| p.trim()).collect();
return Some(ObjectInfo {
name: "function_params".to_string(),
object_type: ObjectType::Function,
definition_line: line_num,
usage_lines: Vec::new(),
is_escaping: false,
escape_path: EscapePath {
escapes_to_global: false,
escapes_to_argument: true, escapes_to_return: false,
escapes_to_property: false,
escape_functions: HashSet::new(),
},
});
}
}
}
None
}
fn analyze_escape_path(&self, obj: &mut ObjectInfo, lines: &[&str]) {
for (line_num, line) in lines.iter().enumerate() {
if self.is_object_used(line, &obj.name) {
obj.usage_lines.push(line_num);
self.analyze_line_for_escape(obj, line);
}
}
obj.escape_level = self.determine_escape_level(&obj.escape_path);
obj.is_escaping = obj.escape_level != EscapeLevel::NoEscape;
}
fn analyze_line_for_escape(&self, obj: &mut ObjectInfo, line: &str) {
let trimmed: _ = line.trim();
if trimmed.contains("return ") && line.contains(&obj.name) {
obj.escape_path.escapes_to_return = true;
}
if line.contains('(') && line.contains(&obj.name) {
obj.escape_path.escapes_to_argument = true;
}
if trimmed.starts_with("window.") || trimmed.starts_with("global.") || trimmed.starts_with("globalThis.") {
if line.contains(&obj.name) {
obj.escape_path.escapes_to_global = true;
}
}
if trimmed.contains('.') && line.contains(&obj.name) {
if let Some(eq_pos) = trimmed.find('=') {
let left: _ = &trimmed[..eq_pos];
if left.contains('.') && !left.contains(&obj.name) {
obj.escape_path.escapes_to_property = true;
}
}
}
if let Some(func_name) = self.extract_called_function(line) {
if self.is_escaping_function(&func_name) {
obj.escape_path.escape_functions.insert(func_name);
}
}
}
fn determine_escape_level(&self, escape_path: &EscapePath) -> EscapeLevel {
if escape_path.escapes_to_thread {
EscapeLevel::ThreadEscape
} else if escape_path.escapes_to_global {
EscapeLevel::GlobalEscape
} else if escape_path.escapes_to_argument || escape_path.escapes_to_return {
EscapeLevel::ArgEscape
} else {
EscapeLevel::NoEscape
}
}
fn determine_object_type(&self, value: &str) -> ObjectType {
let trimmed: _ = value.trim();
if trimmed.starts_with('{') {
ObjectType::PlainObject
} else if trimmed.starts_with('[') {
ObjectType::Array
} else if trimmed.starts_with("function") || trimmed.starts_with("(") {
ObjectType::Function
} else if trimmed.starts_with("class ") {
ObjectType::Class
} else {
ObjectType::Other
}
}
fn is_valid_identifier(&self, name: &str) -> bool {
if name.is_empty() {
return false;
}
let mut chars = name.chars();
let first: _ = chars.next().unwrap();
if !first.is_alphabetic() && first != '_' && first != '$' {
return false;
}
for c in chars {
if !c.is_alphanumeric() && c != '_' && c != '$' {
return false;
}
}
true
}
fn is_object_used(&self, line: &str, obj_name: &str) -> bool {
if line.trim().starts_with("let ") || line.trim().starts_with("const ") || line.trim().starts_with("var ") {
if line.contains(obj_name) && line.contains('=') {
return false;
}
}
line.contains(obj_name)
}
fn extract_called_function(&self, line: &str) -> Option<String> {
let paren_pos: _ = line.find('(')?;
let before_paren: _ = &line[..paren_pos];
let mut func_start = before_paren.len();
for (i, c) in before_paren.char_indices().rev() {
if !c.is_alphanumeric() && c != '_' && c != '$' && c != '.' {
func_start = i + 1;
break;
}
}
Some(before_paren[func_start..].to_string())
}
fn is_escaping_function(&self, func_name: &str) -> bool {
let escaping_functions: _ = [
"JSON.stringify",
"JSON.parse",
"console.log",
"console.error",
"Promise.resolve",
"setTimeout",
"setInterval",
"fetch",
"Array.from",
"Object.assign",
"Object.keys",
"Object.values",
];
escaping_functions.contains(&func_name)
}
fn calculate_stack_allocation_savings(&self, obj_info: &ObjectInfo) -> f64 {
let base_savings: _ = 50.0; match obj_info.object_type {
ObjectType::PlainObject => base_savings * 1.0,
ObjectType::Array => base_savings * 1.2,
ObjectType::Function => base_savings * 0.8,
ObjectType::Class => base_savings * 1.5,
ObjectType::Other => base_savings * 0.5,
}
}
pub fn record_escape_analysis(&mut self, code_hash: &str, can_stack: bool, savings: f64) {
let stats: _ = self.analysis_history.entry(code_hash.to_string()).or_insert(EscapeStats {
total_analyzed: 0,
stack_allocated: 0,
heap_allocated: 0,
total_savings: 0.0,
last_analysis: Instant::now(),
});
stats.total_analyzed += 1;
if can_stack {
stats.stack_allocated += 1;
} else {
stats.heap_allocated += 1;
}
stats.total_savings += savings;
stats.last_analysis = Instant::now();
}
pub fn get_escape_stats(&self, code_hash: &str) -> Option<&EscapeStats> {
self.analysis_history.get(code_hash)
}
pub fn reset_stats(&mut self) {
self.analysis_history.clear();
}
}
impl Default for EscapeAnalysisOptimizer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::{BTreeMap};
use std::time::{Duration, Instant};
use std::collections::{HashMap, HashSet};
#[test]
fn test_plain_object_escape_analysis() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
let code: _ = r#"
let obj: _ = { value: 42 };
console.log(obj.value);
"#;
let objects: _ = optimizer.analyze_escape(code);
assert_eq!(objects.len(), 1);
assert_eq!(objects[0].name, "obj");
assert_eq!(objects[0].object_type, ObjectType::PlainObject);
assert_eq!(objects[0].escape_level, EscapeLevel::NoEscape);
}
#[test]
fn test_global_escape_detection() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
let code: _ = r#"
let obj: _ = { value: 42 };
global.obj = obj;
"#;
let objects: _ = optimizer.analyze_escape(code);
assert_eq!(objects.len(), 1);
assert!(objects[0].escape_path.escapes_to_global);
assert_eq!(objects[0].escape_level, EscapeLevel::GlobalEscape);
}
#[test]
fn test_argument_escape_detection() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
let code: _ = r#"
let obj: _ = { value: 42 };
console.log(obj);
"#;
let objects: _ = optimizer.analyze_escape(code);
assert_eq!(objects.len(), 1);
assert!(objects[0].escape_path.escapes_to_argument);
}
#[test]
fn test_return_escape_detection() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
let code: _ = r#"
function createObj() {
let obj: _ = { value: 42 };
return obj;
}
"#;
let objects: _ = optimizer.analyze_escape(code);
let obj_count: _ = objects.iter().filter(|o| o.name == "obj").count();
assert!(obj_count >= 1);
for obj in &objects {
if obj.name == "obj" && obj.definition_line > 1 {
assert!(obj.escape_path.escapes_to_return);
}
}
}
#[test]
fn test_no_escape_object() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
let code: _ = r#"
let obj: _ = { value: 42 };
let _x: _ = obj.value;
let _y: _ = obj.value * 2;
"#;
let objects: _ = optimizer.analyze_escape(code);
assert_eq!(objects.len(), 1);
assert_eq!(objects[0].escape_level, EscapeLevel::NoEscape);
}
#[test]
fn test_array_escape_analysis() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
let code: _ = r#"
let arr: _ = [1, 2, 3];
arr.push(4);
"#;
let objects: _ = optimizer.analyze_escape(code);
assert_eq!(objects.len(), 1);
assert_eq!(objects[0].object_type, ObjectType::Array);
}
#[test]
fn test_function_parameter_escape() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
let code: _ = r#"
function process(obj) {
console.log(obj);
}
"#;
let objects: _ = optimizer.analyze_escape(code);
assert!(objects.iter().any(|o| o.escape_path.escapes_to_argument));
}
#[test]
fn test_escape_decision_no_escape() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
let obj_info: _ = ObjectInfo {
name: "localObj".to_string(),
object_type: ObjectType::PlainObject,
definition_line: 0,
usage_lines: vec![1, 2],
is_escaping: false,
escape_path: EscapePath {
escapes_to_global: false,
escapes_to_argument: false,
escapes_to_return: false,
escapes_to_property: false,
escape_functions: HashSet::new(),
},
};
let decision: _ = optimizer.make_escape_decision(&obj_info);
assert!(decision.can_stack_allocate);
assert_eq!(decision.escape_level, EscapeLevel::NoEscape);
assert!(decision.allocation_savings > 0.0);
}
#[test]
fn test_escape_decision_global_escape() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
let obj_info: _ = ObjectInfo {
name: "globalObj".to_string(),
object_type: ObjectType::PlainObject,
definition_line: 0,
usage_lines: vec![1, 2],
is_escaping: true,
escape_path: EscapePath {
escapes_to_global: true,
escapes_to_argument: false,
escapes_to_return: false,
escapes_to_property: false,
escape_functions: HashSet::new(),
},
};
let decision: _ = optimizer.make_escape_decision(&obj_info);
assert!(!decision.can_stack_allocate);
assert_eq!(decision.escape_level, EscapeLevel::GlobalEscape);
assert_eq!(decision.allocation_savings, 0.0);
}
#[test]
fn test_object_type_detection() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
assert_eq!(
optimizer.determine_object_type("{ value: 42 }"),
ObjectType::PlainObject
);
assert_eq!(
optimizer.determine_object_type("[1, 2, 3]"),
ObjectType::Array
);
assert_eq!(
optimizer.determine_object_type("function() {}"),
ObjectType::Function
);
assert_eq!(
optimizer.determine_object_type("class MyClass {}"),
ObjectType::Class
);
}
#[test]
fn test_escaping_function_detection() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
assert!(optimizer.is_escaping_function("console.log"));
assert!(optimizer.is_escaping_function("JSON.stringify"));
assert!(optimizer.is_escaping_function("setTimeout"));
assert!(!optimizer.is_escaping_function("localFunction"));
}
#[test]
fn test_called_function_extraction() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
assert_eq!(
optimizer.extract_called_function("console.log(obj)"),
Some("console.log".to_string());
assert_eq!(
optimizer.extract_called_function("Math.random()"),
Some("Math.random".to_string());
assert_eq!(
optimizer.extract_called_function("obj.method()"),
Some("obj.method".to_string());
}
#[test]
fn test_escape_analysis_history() {
let mut optimizer = EscapeAnalysisOptimizer::new();
let code_hash: _ = "test_code";
optimizer.record_escape_analysis(code_hash, true, 50.0);
optimizer.record_escape_analysis(code_hash, false, 0.0);
let stats: _ = optimizer.get_escape_stats(code_hash).unwrap();
assert_eq!(stats.total_analyzed, 2);
assert_eq!(stats.stack_allocated, 1);
assert_eq!(stats.heap_allocated, 1);
assert_eq!(stats.total_savings, 50.0);
optimizer.reset_stats();
assert!(optimizer.get_escape_stats(code_hash).is_none());
}
#[test]
fn test_identifier_validation() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
assert!(optimizer.is_valid_identifier("validName"));
assert!(optimizer.is_valid_identifier("_private"));
assert!(optimizer.is_valid_identifier("$global"));
assert!(optimizer.is_valid_identifier("a123"));
assert!(!optimizer.is_valid_identifier("123abc"));
assert!(!optimizer.is_valid_identifier(""));
assert!(!optimizer.is_valid_identifier("has-dash"));
}
#[test]
fn test_complex_escape_scenario() {
let optimizer: _ = EscapeAnalysisOptimizer::new();
let code: _ = r#"
let obj1: _ = { value: 1 };
let obj2: _ = { value: 2 };
obj1.prop = obj2;
global.shared = obj1;
return obj2;
"#;
let objects: _ = optimizer.analyze_escape(code);
assert_eq!(objects.len(), 2);
for obj in &objects {
if obj.name == "obj1" {
assert!(obj.escape_path.escapes_to_global);
assert!(obj.escape_path.escapes_to_property);
} else if obj.name == "obj2" {
assert!(obj.escape_path.escapes_to_return);
}
}
}
}