in hphp/hack/src/hackc/emitter/emit_expression.rs [5247:5648]
fn emit_base_<'a, 'arena, 'decl>(
e: &mut Emitter<'arena, 'decl>,
env: &Env<'a, 'arena>,
expr: &ast::Expr,
mode: MOpMode,
is_object: bool,
notice: BareThisOp,
null_coalesce_assignment: bool,
base_offset: StackIndex,
rhs_stack_size: StackIndex,
inout_param_info: Option<(usize, &inout_locals::AliasInfoMap<'_>)>,
readonly_op: ReadonlyOp,
) -> Result<ArrayGetBase<'arena>> {
let alloc = env.arena;
let pos = &expr.1;
let expr_ = &expr.2;
let base_mode = if mode == MOpMode::InOut {
MOpMode::Warn
} else {
mode
};
let local_temp_kind = get_local_temp_kind(env, true, inout_param_info, Some(expr));
let emit_default = |e: &mut Emitter<'arena, 'decl>,
base_instrs,
cls_instrs,
setup_instrs,
base_stack_size,
cls_stack_size| {
match local_temp_kind {
Some(local_temp) => {
let local = e.local_gen_mut().get_unnamed();
ArrayGetBase::Inout {
load: ArrayGetBaseData {
base_instrs: vec![(base_instrs, Some((local, local_temp)))],
cls_instrs,
setup_instrs,
base_stack_size,
cls_stack_size,
},
store: instr::basel(local, MOpMode::Define, ReadonlyOp::Any),
}
}
_ => ArrayGetBase::Regular(ArrayGetBaseData {
base_instrs,
cls_instrs,
setup_instrs,
base_stack_size,
cls_stack_size,
}),
}
};
// Called when emitting a base with MOpMode::Define on a Readonly expression
let emit_readonly_lval_base = |
e: &mut Emitter<'arena, 'decl>,
env: &Env<'a, 'arena>,
inner_expr: &ast::Expr, // expression inside of readonly expression
| -> Option<Result<ArrayGetBase<'_>>> {
// Readonly local variable requires a CheckROCOW
if let aast::Expr(_, _, Expr_::Lvar(x)) = &*inner_expr {
if !is_local_this(env, &x.1) || env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS) {
match get_local(e, env, &x.0, &(x.1).1) {
Ok(v) => {
let base_instr = if local_temp_kind.is_some() {
instr::cgetquietl(v)
} else {
instr::empty()
};
Some(Ok(emit_default(
e,
base_instr,
instr::empty(),
instr::basel( v, MOpMode::Define, ReadonlyOp::CheckROCOW),
0,
0,
)))
}
Err(e) => Some(Err(e))
}
} else {
None // Found a local variable case that does not work
}
} else {
// The only other reasonable case is if the inner expression
// is a ClassGet, in which case we can use the default emit_base_ logic to handle
None // Otherwise, ignore readonly
}
};
let emit_expr_default =
|e: &mut Emitter<'arena, 'decl>, env, expr: &ast::Expr| -> Result<ArrayGetBase<'_>> {
let base_expr_instrs = emit_expr(e, env, expr)?;
Ok(emit_default(
e,
base_expr_instrs,
instr::empty(),
emit_pos_then(pos, instr::basec(base_offset, base_mode)),
1,
0,
))
};
use ast::Expr_;
match expr_ {
// Readonly expression in assignment
Expr_::ReadonlyExpr(r) if base_mode == MOpMode::Define => {
if let Some(result) = emit_readonly_lval_base(e, env, r) {
result
} else {
// If we're not able to emit special readonly expression instructions, emit code as if readonly
// expression does not exist
emit_base_(
e,
env,
r,
mode,
is_object,
notice,
null_coalesce_assignment,
base_offset,
rhs_stack_size,
inout_param_info,
readonly_op,
)
}
}
Expr_::Lvar(x) if superglobals::is_superglobal(&(x.1).1) => {
let base_instrs = emit_pos_then(
&x.0,
instr::string(alloc, string_utils::locals::strip_dollar(&(x.1).1)),
);
Ok(emit_default(
e,
base_instrs,
instr::empty(),
instr::basegc(base_offset, base_mode),
1,
0,
))
}
Expr_::Lvar(x) if is_object && (x.1).1 == special_idents::THIS => {
let base_instrs = emit_pos_then(&x.0, instr::checkthis());
Ok(emit_default(
e,
base_instrs,
instr::empty(),
instr::baseh(),
0,
0,
))
}
Expr_::Lvar(x)
if !is_local_this(env, &x.1) || env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS) =>
{
let v = get_local(e, env, &x.0, &(x.1).1)?;
let base_instr = if local_temp_kind.is_some() {
instr::cgetquietl(v)
} else {
instr::empty()
};
Ok(emit_default(
e,
base_instr,
instr::empty(),
instr::basel(v, base_mode, ReadonlyOp::Any),
0,
0,
))
}
Expr_::Lvar(lid) => {
let local = emit_local(e, env, notice, lid)?;
Ok(emit_default(
e,
local,
instr::empty(),
instr::basec(base_offset, base_mode),
1,
0,
))
}
Expr_::ArrayGet(x) => match (&(x.0).1, x.1.as_ref()) {
// $a[] can not be used as the base of an array get unless as an lval
(_, None) if !env.flags.contains(env::Flags::ALLOWS_ARRAY_APPEND) => {
Err(Error::fatal_runtime(pos, "Can't use [] for reading"))
}
// base is in turn array_get - do a specific handling for inout params
// if necessary
(_, opt_elem_expr) => {
let base_expr = &x.0;
let local_temp_kind =
get_local_temp_kind(env, false, inout_param_info, opt_elem_expr);
let (elem_instrs, elem_stack_size) = emit_elem(
e,
env,
opt_elem_expr,
local_temp_kind,
null_coalesce_assignment,
)?;
let base_result = emit_base_(
e,
env,
base_expr,
mode,
false,
notice,
null_coalesce_assignment,
base_offset + elem_stack_size,
rhs_stack_size,
inout_param_info,
readonly_op, // continue passing readonly enforcement up
)?;
let cls_stack_size = match &base_result {
ArrayGetBase::Regular(base) => base.cls_stack_size,
ArrayGetBase::Inout { load, .. } => load.cls_stack_size,
};
let (mk, warninstr) = get_elem_member_key(
e,
env,
base_offset + cls_stack_size,
opt_elem_expr,
null_coalesce_assignment,
)?;
let make_setup_instrs = |base_setup_instrs: InstrSeq<'arena>| {
InstrSeq::gather(vec![warninstr, base_setup_instrs, instr::dim(mode, mk)])
};
Ok(match (base_result, local_temp_kind) {
// both base and index don't use temps - fallback to default handler
(ArrayGetBase::Regular(base), None) => emit_default(
e,
InstrSeq::gather(vec![base.base_instrs, elem_instrs]),
base.cls_instrs,
make_setup_instrs(base.setup_instrs),
base.base_stack_size + elem_stack_size,
base.cls_stack_size,
),
// base does not need temps but index does
(ArrayGetBase::Regular(base), Some(local_temp)) => {
let local = e.local_gen_mut().get_unnamed();
let base_instrs = InstrSeq::gather(vec![base.base_instrs, elem_instrs]);
ArrayGetBase::Inout {
load: ArrayGetBaseData {
// store result of instr_begin to temp
base_instrs: vec![(base_instrs, Some((local, local_temp)))],
cls_instrs: base.cls_instrs,
setup_instrs: make_setup_instrs(base.setup_instrs),
base_stack_size: base.base_stack_size + elem_stack_size,
cls_stack_size: base.cls_stack_size,
},
store: emit_store_for_simple_base(
e,
env,
pos,
elem_stack_size,
base_expr,
local,
true,
readonly_op,
)?,
}
}
// base needs temps, index - does not
(
ArrayGetBase::Inout {
load:
ArrayGetBaseData {
mut base_instrs,
cls_instrs,
setup_instrs,
base_stack_size,
cls_stack_size,
},
store,
},
None,
) => {
base_instrs.push((elem_instrs, None));
ArrayGetBase::Inout {
load: ArrayGetBaseData {
base_instrs,
cls_instrs,
setup_instrs: make_setup_instrs(setup_instrs),
base_stack_size: base_stack_size + elem_stack_size,
cls_stack_size,
},
store: InstrSeq::gather(vec![store, instr::dim(MOpMode::Define, mk)]),
}
}
// both base and index needs locals
(
ArrayGetBase::Inout {
load:
ArrayGetBaseData {
mut base_instrs,
cls_instrs,
setup_instrs,
base_stack_size,
cls_stack_size,
},
store,
},
Some(local_kind),
) => {
let local = e.local_gen_mut().get_unnamed();
base_instrs.push((elem_instrs, Some((local, local_kind))));
ArrayGetBase::Inout {
load: ArrayGetBaseData {
base_instrs,
cls_instrs,
setup_instrs: make_setup_instrs(setup_instrs),
base_stack_size: base_stack_size + elem_stack_size,
cls_stack_size,
},
store: InstrSeq::gather(vec![
store,
instr::dim(MOpMode::Define, MemberKey::EL(local, ReadonlyOp::Any)),
]),
}
}
})
}
},
Expr_::ObjGet(x) if x.as_ref().3 == ast::PropOrMethod::IsProp => {
let (base_expr, prop_expr, null_flavor, _) = &**x;
Ok(match prop_expr.2.as_id() {
Some(ast_defs::Id(_, s)) if string_utils::is_xhp(s) => {
let base_instrs = emit_xhp_obj_get(e, env, pos, base_expr, s, null_flavor)?;
emit_default(
e,
base_instrs,
instr::empty(),
instr::basec(base_offset, base_mode),
1,
0,
)
}
_ => {
let prop_stack_size = emit_prop_expr(
e,
env,
null_flavor,
0,
prop_expr,
null_coalesce_assignment,
ReadonlyOp::Any, // just getting stack size here
)?
.2;
let (
base_expr_instrs_begin,
base_expr_instrs_end,
base_setup_instrs,
base_stack_size,
cls_stack_size,
) = emit_base(
e,
env,
base_expr,
mode,
true,
BareThisOp::Notice,
null_coalesce_assignment,
base_offset + prop_stack_size,
rhs_stack_size,
ReadonlyOp::Mutable, // the rest of the base must be completely mutable
)?;
let (mk, prop_instrs, _) = emit_prop_expr(
e,
env,
null_flavor,
base_offset + cls_stack_size,
prop_expr,
null_coalesce_assignment,
readonly_op, // use the current enforcement
)?;
let total_stack_size = prop_stack_size + base_stack_size;
let final_instr = instr::dim(mode, mk);
emit_default(
e,
InstrSeq::gather(vec![base_expr_instrs_begin, prop_instrs]),
base_expr_instrs_end,
InstrSeq::gather(vec![base_setup_instrs, final_instr]),
total_stack_size,
cls_stack_size,
)
}
})
}
Expr_::ClassGet(x) => {
let (cid, prop, _) = &**x;
let cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
let (cexpr_begin, cexpr_end) = emit_class_expr(e, env, cexpr, prop)?;
Ok(emit_default(
e,
cexpr_begin,
cexpr_end,
instr::basesc(base_offset + 1, rhs_stack_size, base_mode, readonly_op),
1,
1,
))
}
_ => emit_expr_default(e, env, expr),
}
}