Skip to content

Commit

Permalink
feat: done API call aggregation
Browse files Browse the repository at this point in the history
  • Loading branch information
SichangHe committed Oct 8, 2024
1 parent 10f3229 commit a83e481
Showing 1 changed file with 106 additions and 27 deletions.
133 changes: 106 additions & 27 deletions jsphere_vv8_log/src/aggregating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ pub struct RecordAggregate {
scripts: HashMap<i32, ScriptAggregate>,
current_script_id: i32,
interaction_injected: bool,
// TODO: Record API calls.
}

impl RecordAggregate {
pub fn add(&mut self, line: usize, record: LogRecord) -> Result<()> {
match record {
let maybe_get_set = match record {
LogRecord::IsolateContext { address } => {
debug!(line, "Ignoring isolate context {address:#x}");
None
}

LogRecord::WindowOrigin { value } => debug!(line, ?value, "Ignoring window.origin"),
LogRecord::WindowOrigin { value } => {debug!(line, ?value, "Ignoring window.origin"); None},

LogRecord::ScriptProvenance { id, name, source } => {
let name = name.try_into()?;
Expand Down Expand Up @@ -46,66 +46,144 @@ impl RecordAggregate {
name,
source,
injection_type,
api_calls: Default::default(),
};
if let Some(prev_script) = self.scripts.insert(id, script) {
bail!("Overwrote script {id}: {prev_script:?}");
}
None
}

LogRecord::ExecutionContext { script_id } => {
let script = self
.scripts
.get(&script_id)
.context("Unknown execution context script ID")?;
self.current_script_id = script_id;
let script = self.current_script()?;
if matches!(script.injection_type, ScriptInjectionType::Interaction) {
// Entering a context with an interaction script is
// the only way we know an interaction started for sure.
self.interaction_injected = true;
}
self.current_script_id = script_id;
None
}

// TODO: Record API calls, alone with if they could be triggered by
// interactions.
// Ignore user function calls.
LogRecord::FunctionCall {
offset,
method,
is_user_fn,
receiver,
arguments,
} => todo!(),
is_user_fn: true, ..
} => None,

LogRecord::FunctionCall {
method, receiver, .. // Ignore arguments, etc. for now.
} => {
let this = match receiver {
JSValue::Object { constructor, .. } => Some(constructor),
JSValue::Function { name, is_user_fn } => {
match is_user_fn {
true => None, // Ignore user functions.
false => Some(name),
}
}
JSValue::Lambda => None, // Ignore lambda calls.
_ => bail!("{line}: Unexpected function call receiver: {receiver:?}"),
};
if let Some(this) = this {
let api_call = ApiCall {
api_type: ApiType::Function,
this,
attr: Some(method),
may_be_interaction: self.interaction_injected,
};
self.current_script()?.api_calls.push(api_call);
}
None
}

// Ignore user construction calls.
LogRecord::ConstructionCall {
offset,
method,
is_user_fn,
arguments,
} => todo!(),
is_user_fn: true, ..
} => None,

LogRecord::ConstructionCall {
method,.. // Ignore arguments, etc. for now.
} => {
let api_call = ApiCall {
api_type: ApiType::Construction,
this: method,
attr: None,
may_be_interaction: self.interaction_injected,
};
self.current_script()?.api_calls.push(api_call);
None
},

LogRecord::GetProperty {
offset,
object,
property,
} => todo!(),
..
} => Some((ApiType::Get, object, property)),

LogRecord::SetProperty {
offset,
| LogRecord::SetProperty {
object,
property,
value,
} => todo!(),
..
} => Some((ApiType::Set, object, property)),
};

// Handle get/set calls after all other types.
if let Some((api_type, object, property)) = maybe_get_set {
let this = match object {
JSValue::Object { constructor, .. } => Some(constructor),
JSValue::ObjectLiteral { .. } => None, // Ignore object literals.
_ => bail!("{line}: Unexpected get/set on object: {object:?}"),
};
if let Some(this) = this {
let attr = match property {
JSValue::String(attr) => Some(attr),
JSValue::Int(_) | JSValue::Float(_) | JSValue::Unsure => None, // Ignore numbers or internal values.
_ => bail!("{line}: Unexpected get/set property: {property:?}"),
};
if attr.is_some() {
let api_call = ApiCall {
api_type,
this,
attr,
may_be_interaction: self.interaction_injected,
};
self.current_script()?.api_calls.push(api_call);
}
}
}
Ok(())
}

fn current_script(&mut self) -> Result<&mut ScriptAggregate> {
self.scripts
.get_mut(&self.current_script_id)
.context("Unknown execution context script ID")
}
}

/// A script that was executed and its aggregate information.
#[pub_fields]
#[derive_enum_everything]
pub struct ScriptAggregate {
line: usize,
name: ScriptName,
source: String,
injection_type: ScriptInjectionType,
api_calls: Vec<ApiCall>,
}

/// A browser JS API call.
///
/// Arguments are ignored.
#[derive_everything]
pub struct ApiCall {
api_type: ApiType,
this: String,
attr: Option<String>,
may_be_interaction: bool,
}

/// The type of API call.
#[derive_everything]
pub enum ApiType {
#[default]
Expand All @@ -128,6 +206,7 @@ pub enum ScriptName {
Eval { parent_script_id: i32 },
}

/// Whether the script was injected, and if so, whether it was for interaction.
#[derive_everything]
pub enum ScriptInjectionType {
#[default]
Expand Down

0 comments on commit a83e481

Please sign in to comment.