use super::builtins::execute_builtin_function;
use super::execute_data::{
clone_val, is_temp_ref, is_var_ref, resolve_operand, result_slot, ExecResult, ExecuteData,
};
use super::opcodes::{Op, OpArray, Opcode};
use crate::engine::facade::{bool_val, long_val};
use crate::engine::types::{PhpType, PhpValue, Val};
#[allow(dead_code)]
fn val_to_var_name(val: &Val) -> Option<String> {
if let PhpValue::String(ref s) = val.value {
let name = s.as_str();
Some(if name.starts_with('$') {
name[1..].to_string()
} else {
name.to_string()
})
} else {
None
}
}
#[allow(dead_code)]
pub(crate) fn execute_opcode(
op: &Op,
execute_data: &mut ExecuteData,
) -> Result<ExecResult, String> {
match op.opcode {
Opcode::Nop => Ok(ExecResult::Continue),
Opcode::FetchVar => {
let val = resolve_operand(&op.op1, execute_data);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, val);
}
Ok(ExecResult::Continue)
}
Opcode::Add => execute_binary_op(op, execute_data, |a, b| {
crate::engine::operators::zval_add(a, b)
}),
Opcode::Sub => execute_binary_op(op, execute_data, |a, b| {
crate::engine::operators::zval_sub(a, b)
}),
Opcode::Mul => execute_binary_op(op, execute_data, |a, b| {
crate::engine::operators::zval_mul(a, b)
}),
Opcode::Div => {
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
match crate::engine::operators::zval_div(&op1, &op2) {
Ok(result) => {
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Err(e) => Err(e),
}
}
Opcode::Mod => {
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let v1 = crate::engine::operators::zval_get_long(&op1);
let v2 = crate::engine::operators::zval_get_long(&op2);
if v2 == 0 {
return Err("Division by zero".into());
}
let result = Val::new(PhpValue::Long(v1 % v2), PhpType::Long);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::Pow => {
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let v1 = crate::engine::operators::zval_get_double(&op1);
let v2 = crate::engine::operators::zval_get_double(&op2);
let result = Val::new(PhpValue::Double(v1.powf(v2)), PhpType::Double);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::Sl => execute_bitwise_op(op, execute_data, |a, b| a << b),
Opcode::Sr => execute_bitwise_op(op, execute_data, |a, b| a >> b),
Opcode::Assign => {
let val = resolve_operand(&op.op2, execute_data);
if let PhpValue::String(var_name) = &op.op1.value {
let name = var_name.as_str();
let clean = if name.starts_with('$') {
&name[1..]
} else {
name
};
execute_data.set_var(clean, clone_val(&val));
}
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, val);
}
Ok(ExecResult::Continue)
}
Opcode::Concat => {
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let s1 = crate::engine::operators::zval_get_string(&op1);
let s2 = crate::engine::operators::zval_get_string(&op2);
let combined = format!("{}{}", s1.as_str(), s2.as_str());
let result = Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(
&combined, false,
))),
PhpType::String,
);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::Echo => {
let val = resolve_operand(&op.op1, execute_data);
let s = crate::engine::operators::zval_get_string(&val);
let _ = crate::php::output::php_output_write(s.as_bytes());
Ok(ExecResult::Continue)
}
Opcode::Return => {
let val = resolve_operand(&op.op1, execute_data);
Ok(ExecResult::Return(val))
}
Opcode::Jmp => Ok(ExecResult::Jump(op.extended_value)),
Opcode::JmpZ => {
let val = resolve_operand(&op.op1, execute_data);
let b = crate::engine::operators::zval_get_bool(&val);
if !b {
Ok(ExecResult::Jump(op.extended_value))
} else {
Ok(ExecResult::Continue)
}
}
Opcode::JmpNZ => {
let val = resolve_operand(&op.op1, execute_data);
let b = crate::engine::operators::zval_get_bool(&val);
if b {
Ok(ExecResult::Jump(op.extended_value))
} else {
Ok(ExecResult::Continue)
}
}
Opcode::IsEqual => execute_cmp_op(op, execute_data, |c| c == 0),
Opcode::IsNotEqual => execute_cmp_op(op, execute_data, |c| c != 0),
Opcode::IsSmaller => execute_cmp_op(op, execute_data, |c| c < 0),
Opcode::IsSmallerOrEqual => execute_cmp_op(op, execute_data, |c| c <= 0),
Opcode::IsIdentical => {
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let same_type = op1.get_type() == op2.get_type();
let eq = same_type && crate::engine::operators::zval_compare(&op1, &op2) == 0;
let result = Val::new(
PhpValue::Long(if eq { 1 } else { 0 }),
if eq { PhpType::True } else { PhpType::False },
);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::IsNotIdentical => {
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let same_type = op1.get_type() == op2.get_type();
let neq = !same_type || crate::engine::operators::zval_compare(&op1, &op2) != 0;
let result = Val::new(
PhpValue::Long(if neq { 1 } else { 0 }),
if neq { PhpType::True } else { PhpType::False },
);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::BwOr => execute_bitwise_op(op, execute_data, |a, b| a | b),
Opcode::BwAnd => execute_bitwise_op(op, execute_data, |a, b| a & b),
Opcode::BwXor => execute_bitwise_op(op, execute_data, |a, b| a ^ b),
Opcode::BoolNot => {
let val = resolve_operand(&op.op1, execute_data);
let b = !crate::engine::operators::zval_get_bool(&val);
let result = Val::new(
PhpValue::Long(if b { 1 } else { 0 }),
if b { PhpType::True } else { PhpType::False },
);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::BoolXor => {
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let r = crate::engine::operators::zval_get_bool(&op1)
^ crate::engine::operators::zval_get_bool(&op2);
let result = Val::new(
PhpValue::Long(if r { 1 } else { 0 }),
if r { PhpType::True } else { PhpType::False },
);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::BoolAnd => {
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let r = crate::engine::operators::zval_get_bool(&op1)
&& crate::engine::operators::zval_get_bool(&op2);
let result = Val::new(
PhpValue::Long(if r { 1 } else { 0 }),
if r { PhpType::True } else { PhpType::False },
);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::BoolOr => {
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let r = crate::engine::operators::zval_get_bool(&op1)
|| crate::engine::operators::zval_get_bool(&op2);
let result = Val::new(
PhpValue::Long(if r { 1 } else { 0 }),
if r { PhpType::True } else { PhpType::False },
);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::InitFCall => {
execute_data.call_args.clear();
Ok(ExecResult::Continue)
}
Opcode::SendVal => {
let val = resolve_operand(&op.op1, execute_data);
execute_data.call_args.push(val);
Ok(ExecResult::Continue)
}
Opcode::DoFCall => execute_do_fcall(op, execute_data),
Opcode::Include => execute_include(op, execute_data),
Opcode::InitArray => execute_init_array(op, execute_data),
Opcode::AddArrayElement => execute_add_array_element(op, execute_data),
Opcode::FetchDim => execute_fetch_dim(op, execute_data),
Opcode::NewObj => execute_new_obj(op, execute_data),
Opcode::FetchObjProp => execute_fetch_obj_prop(op, execute_data),
Opcode::AssignObjProp => execute_assign_obj_prop(op, execute_data),
Opcode::InitMethodCall => {
execute_data.call_args.clear();
Ok(ExecResult::Continue)
}
Opcode::DoMethodCall => execute_do_method_call(op, execute_data),
Opcode::TypeCheck => {
let var = resolve_operand(&op.op1, execute_data);
let result = Val::new(PhpValue::Long(var.get_type() as i64), PhpType::Long);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::IsSet => {
let var = resolve_operand(&op.op1, execute_data);
let is_set = if let Some(name) = val_to_var_name(&var) {
let v = execute_data.get_var(&name);
bool_val(v.get_type() != PhpType::Null && v.get_type() != PhpType::Undef)
} else {
bool_val(false)
};
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, is_set);
}
Ok(ExecResult::Continue)
}
Opcode::Empty => {
let var = resolve_operand(&op.op1, execute_data);
let is_empty = if let Some(name) = val_to_var_name(&var) {
let v = execute_data.get_var(&name);
bool_val(v.get_type() == PhpType::Null || v.get_type() == PhpType::Undef)
} else {
bool_val(true)
};
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, is_empty);
}
Ok(ExecResult::Continue)
}
Opcode::Unset => {
let var = resolve_operand(&op.op1, execute_data);
if let Some(name) = val_to_var_name(&var) {
execute_data.remove_var(&name);
}
Ok(ExecResult::Continue)
}
Opcode::Count => {
let arr = resolve_operand(&op.op1, execute_data);
let count = match &arr.value {
PhpValue::Array(a) => long_val(a.n_num_of_elements as i64),
PhpValue::String(s) => long_val(s.len as i64),
_ => long_val(0),
};
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, count);
}
Ok(ExecResult::Continue)
}
Opcode::Keys => {
let arr = resolve_operand(&op.op1, execute_data);
match &arr.value {
PhpValue::Array(a) => {
let mut result_arr = crate::engine::types::PhpArray::new();
for bucket in &a.ar_data {
if let Some(key) = &bucket.key {
let key_val = Val::new(
PhpValue::String(Box::new(crate::engine::string::string_init(
key.as_str(),
false,
))),
PhpType::String,
);
let idx = result_arr.n_num_of_elements as u64;
let _ = crate::engine::hash::hash_add_or_update(
&mut result_arr,
None,
idx,
key_val,
0,
);
}
}
let result = Val::new(PhpValue::Array(Box::new(result_arr)), PhpType::Array);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
_ => Err("Keys() requires an array".into()),
}
}
Opcode::Values => {
let arr = resolve_operand(&op.op1, execute_data);
match &arr.value {
PhpValue::Array(a) => {
let mut result_arr = crate::engine::types::PhpArray::new();
for bucket in &a.ar_data {
let cloned = clone_val(&bucket.val);
let idx = result_arr.n_num_of_elements as u64;
let _ = crate::engine::hash::hash_add_or_update(
&mut result_arr,
None,
idx,
cloned,
0,
);
}
let result = Val::new(PhpValue::Array(Box::new(result_arr)), PhpType::Array);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
_ => Err("Values() requires an array".into()),
}
}
Opcode::ArrayDiff => {
let arr1 = resolve_operand(&op.op1, execute_data);
let arr2 = resolve_operand(&op.op2, execute_data);
let result = match (&arr1.value, &arr2.value) {
(PhpValue::Array(a), PhpValue::Array(b)) => {
let is_equal = a.n_num_of_elements == b.n_num_of_elements;
bool_val(is_equal)
}
_ => bool_val(false),
};
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::Coalesce => {
let left = resolve_operand(&op.op1, execute_data);
let result = if left.get_type() != PhpType::Null && left.get_type() != PhpType::Undef {
left
} else {
resolve_operand(&op.op2, execute_data)
};
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
Opcode::JmpNullZ => {
let val = resolve_operand(&op.op1, execute_data);
if val.get_type() == PhpType::Null || val.get_type() == PhpType::Undef {
if let PhpValue::Long(target) = &op.op2.value {
return Ok(ExecResult::Jump(*target as u32));
}
} else {
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, val);
}
}
Ok(ExecResult::Continue)
}
Opcode::QmAssign => {
let val = resolve_operand(&op.op1, execute_data);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, val);
}
Ok(ExecResult::Continue)
}
Opcode::FeReset => {
let arr = resolve_operand(&op.op1, execute_data);
if let PhpValue::Array(_) = arr.value {
let iterator_slot = op.extended_value as usize;
let iterator = Val::new(PhpValue::Long(0), PhpType::Long);
execute_data.set_temp(iterator_slot, iterator);
}
Ok(ExecResult::Continue)
}
Opcode::FeFetch => {
let arr = resolve_operand(&op.op1, execute_data);
let iterator_slot = op.extended_value as usize;
let iterator_val = execute_data.get_temp(iterator_slot);
let current_idx = if let PhpValue::Long(i) = iterator_val.value {
i as u64
} else {
0
};
let (has_more, key, value) = if let PhpValue::Array(ref arr) = arr.value {
if current_idx < arr.n_num_of_elements as u64 {
let bucket = &arr.ar_data.get(current_idx as usize).and_then(|b| Some(b));
if let Some(bucket) = bucket {
let has_more = current_idx + 1 < arr.n_num_of_elements as u64;
let key = if let Some(ref key_str) = bucket.key {
Val::new(PhpValue::String(key_str.clone()), PhpType::String)
} else {
Val::new(PhpValue::Long(current_idx as i64), PhpType::Long)
};
let value = clone_val(&bucket.val);
(has_more, Some(key), Some(value))
} else {
(false, None, None)
}
} else {
(false, None, None)
}
} else {
(false, None, None)
};
let result_slot = if let Some(slot) = result_slot(op) {
slot
} else {
return Ok(ExecResult::Continue);
};
if has_more {
let next_iter =
Val::new(PhpValue::Long((current_idx + 1) as i64), PhpType::Long);
execute_data.set_temp(iterator_slot, next_iter);
}
if let Some(key) = key {
execute_data.set_temp(result_slot, key);
}
if let Some(value) = value {
execute_data.set_temp(iterator_slot, value);
}
if !has_more {
return Ok(ExecResult::Jump(op.extended_value));
}
Ok(ExecResult::Continue)
}
_ => Ok(ExecResult::Continue),
}
}
#[allow(dead_code)]
fn execute_binary_op<F>(
op: &Op,
execute_data: &mut ExecuteData,
operation: F,
) -> Result<ExecResult, String>
where
F: FnOnce(&Val, &Val) -> Val,
{
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let result = operation(&op1, &op2);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
#[allow(dead_code)]
fn execute_cmp_op<F>(
op: &Op,
execute_data: &mut ExecuteData,
predicate: F,
) -> Result<ExecResult, String>
where
F: FnOnce(i32) -> bool,
{
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let cmp = crate::engine::operators::zval_compare(&op1, &op2);
let b = predicate(cmp);
let result = Val::new(
PhpValue::Long(if b { 1 } else { 0 }),
if b { PhpType::True } else { PhpType::False },
);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
#[allow(dead_code)]
fn execute_bitwise_op<F>(
op: &Op,
execute_data: &mut ExecuteData,
operation: F,
) -> Result<ExecResult, String>
where
F: FnOnce(i64, i64) -> i64,
{
let op1 = resolve_operand(&op.op1, execute_data);
let op2 = resolve_operand(&op.op2, execute_data);
let v1 = crate::engine::operators::zval_get_long(&op1);
let v2 = crate::engine::operators::zval_get_long(&op2);
let result = Val::new(PhpValue::Long(operation(v1, v2)), PhpType::Long);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
#[allow(dead_code)]
fn execute_include(op: &Op, execute_data: &mut ExecuteData) -> Result<ExecResult, String> {
let path_val = resolve_operand(&op.op1, execute_data);
let path = crate::engine::operators::zval_get_string(&path_val);
let is_once = op.extended_value == 2 || op.extended_value == 3;
if is_once && execute_data.included_files.contains(path.as_str()) {
return Ok(ExecResult::Continue);
}
match crate::engine::compile::compile_file(path.as_str()) {
Ok(included_op_array) => {
execute_data
.included_files
.insert(path.as_str().to_string());
let result = super::execute::execute_ex(execute_data, &included_op_array);
if result == crate::engine::types::PhpResult::Failure {
return Err(format!(
"Failed to execute included file: {}",
path.as_str()
));
}
Ok(ExecResult::Continue)
}
Err(e) => {
if op.extended_value == 1 || op.extended_value == 3 {
Err(format!("require({}): {}", path.as_str(), e))
} else {
eprintln!("Warning: include({}): {}", path.as_str(), e);
Ok(ExecResult::Continue)
}
}
}
}
#[allow(dead_code)]
fn execute_init_array(op: &Op, execute_data: &mut ExecuteData) -> Result<ExecResult, String> {
let arr = crate::engine::types::PhpArray::new();
let arr_zval = Val::new(PhpValue::Array(Box::new(arr)), PhpType::Array);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, arr_zval);
}
Ok(ExecResult::Continue)
}
#[allow(dead_code)]
fn execute_add_array_element(
op: &Op,
execute_data: &mut ExecuteData,
) -> Result<ExecResult, String> {
if is_temp_ref(&op.op1) {
if let PhpValue::Long(slot_idx) = op.op1.value {
let arr_slot = slot_idx as usize;
let value = resolve_operand(&op.op2, execute_data);
let mut arr_zval = execute_data.get_temp(arr_slot);
if let PhpValue::Array(ref mut arr) = arr_zval.value {
if op.extended_value != 0 {
let key = resolve_operand(&op.result, execute_data);
let key_str = crate::engine::operators::zval_get_string(&key);
let key_zs =
Box::new(crate::engine::string::string_init(key_str.as_str(), false));
let _ =
crate::engine::hash::hash_add_or_update(arr, Some(&*key_zs), 0, value, 0);
} else {
let next_idx = arr.n_num_used as u64;
let _ = crate::engine::hash::hash_add_or_update(arr, None, next_idx, value, 0);
}
}
execute_data.set_temp(arr_slot, arr_zval);
}
}
Ok(ExecResult::Continue)
}
#[allow(dead_code)]
fn execute_fetch_dim(op: &Op, execute_data: &mut ExecuteData) -> Result<ExecResult, String> {
let arr_val = resolve_operand(&op.op1, execute_data);
let idx_val = resolve_operand(&op.op2, execute_data);
let result_val = if let PhpValue::Array(ref arr) = arr_val.value {
match &idx_val.value {
PhpValue::Long(i) => crate::engine::hash::hash_index_find(arr, *i as u64)
.map(|v| clone_val(v))
.unwrap_or_else(|| Val::new(PhpValue::Long(0), PhpType::Null)),
PhpValue::String(s) => crate::engine::hash::hash_find(arr, s)
.map(|v| clone_val(v))
.unwrap_or_else(|| Val::new(PhpValue::Long(0), PhpType::Null)),
_ => Val::new(PhpValue::Long(0), PhpType::Null),
}
} else {
Val::new(PhpValue::Long(0), PhpType::Null)
};
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result_val);
}
Ok(ExecResult::Continue)
}
#[allow(dead_code)]
fn execute_new_obj(op: &Op, execute_data: &mut ExecuteData) -> Result<ExecResult, String> {
let class_name_val = resolve_operand(&op.op1, execute_data);
let class_name = crate::engine::operators::zval_get_string(&class_name_val);
let cn = class_name.as_str();
let mut obj = crate::engine::types::PhpObject::new(cn);
if let Some(ce) = execute_data.class_table.get(cn) {
for (prop_name, prop_val) in &ce.default_properties {
obj.properties
.insert(prop_name.clone(), clone_val(prop_val));
}
}
let obj_zval = Val::new(PhpValue::Object(Box::new(obj)), PhpType::Object);
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, clone_val(&obj_zval));
}
Ok(ExecResult::Continue)
}
#[allow(dead_code)]
fn execute_fetch_obj_prop(op: &Op, execute_data: &mut ExecuteData) -> Result<ExecResult, String> {
let obj_val = resolve_operand(&op.op1, execute_data);
let prop_name_val = resolve_operand(&op.op2, execute_data);
let prop_name = crate::engine::operators::zval_get_string(&prop_name_val);
let result_val = if let PhpValue::Object(ref obj) = obj_val.value {
obj.properties
.get(prop_name.as_str())
.map(|v| clone_val(v))
.unwrap_or_else(|| Val::new(PhpValue::Long(0), PhpType::Null))
} else {
Val::new(PhpValue::Long(0), PhpType::Null)
};
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result_val);
}
Ok(ExecResult::Continue)
}
#[allow(dead_code)]
fn execute_assign_obj_prop(op: &Op, execute_data: &mut ExecuteData) -> Result<ExecResult, String> {
let var_name_val = &op.op1;
let prop_name_val = resolve_operand(&op.op2, execute_data);
let prop_name = crate::engine::operators::zval_get_string(&prop_name_val);
let value = resolve_operand(&op.result, execute_data);
if is_var_ref(var_name_val) {
if let PhpValue::String(ref s) = var_name_val.value {
let vname = s.as_str();
let name = if vname.starts_with('$') {
&vname[1..]
} else {
vname
};
let mut obj_val = execute_data.get_var(name);
if let PhpValue::Object(ref mut obj) = obj_val.value {
obj.properties.insert(prop_name.as_str().to_string(), value);
}
execute_data.set_var(name, obj_val);
}
} else if is_temp_ref(var_name_val) {
if let PhpValue::Long(slot_idx) = var_name_val.value {
let slot = slot_idx as usize;
let mut obj_val = execute_data.get_temp(slot);
if let PhpValue::Object(ref mut obj) = obj_val.value {
obj.properties.insert(prop_name.as_str().to_string(), value);
}
execute_data.set_temp(slot, obj_val);
}
}
Ok(ExecResult::Continue)
}
#[allow(dead_code)]
fn execute_do_method_call(op: &Op, execute_data: &mut ExecuteData) -> Result<ExecResult, String> {
let method_name_val = resolve_operand(&op.op1, execute_data);
let method_name = crate::engine::operators::zval_get_string(&method_name_val);
let obj_val = resolve_operand(&op.op2, execute_data);
if let PhpValue::Object(ref obj) = obj_val.value {
let class_name = obj.class_name.clone();
let method_info: Option<(Vec<String>, Vec<Op>)> = execute_data
.class_table
.get(&class_name)
.and_then(|ce| ce.methods.get(method_name.as_str()))
.map(|m| {
let params = m.params.clone();
let ops: Vec<Op> = m
.op_array
.ops
.iter()
.map(|op| {
Op::new(
op.opcode,
clone_val(&op.op1),
clone_val(&op.op2),
clone_val(&op.result),
op.extended_value,
)
})
.collect();
(params, ops)
});
if let Some((params, ops)) = method_info {
let saved_current_op = execute_data.current_op;
let saved_op_array = execute_data.op_array.take();
execute_data.set_var("this", clone_val(&obj_val));
let args: Vec<Val> = execute_data
.call_args
.iter()
.map(|a| clone_val(a))
.collect();
for (i, param_name) in params.iter().enumerate() {
if let Some(arg) = args.get(i) {
execute_data.set_var(param_name, clone_val(arg));
}
}
let mut method_op_array =
OpArray::new(format!("{}::{}", class_name, method_name.as_str()));
method_op_array.ops = ops;
let (_status, return_val) =
super::execute::execute_ex_returning(execute_data, &method_op_array);
execute_data.op_array = saved_op_array;
execute_data.current_op = saved_current_op;
execute_data.call_args.clear();
if let Some(slot) = result_slot(op) {
if let Some(ret) = return_val {
execute_data.set_temp(slot, ret);
} else {
execute_data.set_temp(slot, Val::new(PhpValue::Long(0), PhpType::Null));
}
}
return Ok(ExecResult::Continue);
}
}
execute_data.call_args.clear();
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, Val::new(PhpValue::Long(0), PhpType::Null));
}
Ok(ExecResult::Continue)
}
#[allow(dead_code)]
fn execute_do_fcall(op: &Op, execute_data: &mut ExecuteData) -> Result<ExecResult, String> {
let func_name = if is_var_ref(&op.op1) || is_temp_ref(&op.op1) {
let resolved = resolve_operand(&op.op1, execute_data);
crate::engine::operators::zval_get_string(&resolved)
.as_str()
.to_string()
} else {
crate::engine::operators::zval_get_string(&op.op1)
.as_str()
.to_string()
};
let args: Vec<Val> = execute_data.call_args.drain(..).collect();
match execute_builtin_function(&func_name, &args, execute_data)? {
Some(result) => {
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, result);
}
Ok(ExecResult::Continue)
}
None => {
let func_data: Option<(Vec<String>, super::opcodes::OpArray)> = execute_data
.function_table
.as_ref()
.and_then(|ft| {
ft.downcast_ref::<crate::engine::compile::function_table::FunctionTable>()
})
.and_then(|ft| ft.lookup_function(&func_name))
.map(|func_op_array| {
let param_names: Vec<String> = func_op_array
.vars
.iter()
.map(|v| {
if let PhpValue::String(ref s) = v.value {
let name = s.as_str();
if name.starts_with('$') {
name[1..].to_string()
} else {
name.to_string()
}
} else {
String::new()
}
})
.collect();
let mut cloned = super::opcodes::OpArray::new(
func_op_array.filename.clone().unwrap_or_default(),
);
cloned.function_name = func_op_array.function_name.clone();
for op in &func_op_array.ops {
cloned.add_op(super::opcodes::Op::new(
op.opcode,
clone_val(&op.op1),
clone_val(&op.op2),
clone_val(&op.result),
op.extended_value,
));
}
(param_names, cloned)
});
if let Some((param_names, func_op_array)) = func_data {
let saved_op = execute_data.current_op;
let saved_op_array = execute_data.op_array.take();
let saved_temps = std::mem::take(&mut execute_data.temp_vars);
let saved_symbol_table = execute_data.symbol_table.take();
execute_data.symbol_table = Some(crate::engine::types::PhpArray::new());
for (i, arg) in args.iter().enumerate() {
if let Some(name) = param_names.get(i) {
if !name.is_empty() {
let clean = if name.starts_with('$') {
&name[1..]
} else {
name.as_str()
};
execute_data.set_var(clean, clone_val(arg));
}
}
}
let (_status, return_val) =
super::execute::execute_ex_returning(execute_data, &func_op_array);
execute_data.symbol_table = saved_symbol_table;
execute_data.temp_vars = saved_temps;
execute_data.op_array = saved_op_array;
execute_data.current_op = saved_op;
if let Some(ret) = return_val {
if let Some(slot) = result_slot(op) {
execute_data.set_temp(slot, ret);
}
}
return Ok(ExecResult::Continue);
}
eprintln!("Warning: Call to undefined function {}()", func_name);
Ok(ExecResult::Continue)
}
}
}