1 // Copyright (c) Facebook, Inc. and its affiliates.
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
5 use hhbc_by_ref_ast_scope::{self as ast_scope, Lambda, Scope, ScopeItem};
6 use hhbc_by_ref_emit_attribute as emit_attribute;
7 use hhbc_by_ref_emit_body as emit_body;
8 use hhbc_by_ref_emit_fatal::{emit_fatal_runtimeomitframe, raise_fatal_parse};
9 use hhbc_by_ref_emit_memoize_helpers as emit_memoize_helpers;
10 use hhbc_by_ref_emit_native_opcode as emit_native_opcode;
11 use hhbc_by_ref_env::emitter::Emitter;
12 use hhbc_by_ref_hhas_attribute as hhas_attribute;
13 use hhbc_by_ref_hhas_coeffects::HhasCoeffects;
14 use hhbc_by_ref_hhas_method::{HhasMethod, HhasMethodFlags};
15 use hhbc_by_ref_hhas_pos::Span;
16 use hhbc_by_ref_hhbc_id::{method, Id};
17 use hhbc_by_ref_hhbc_string_utils as string_utils;
18 use hhbc_by_ref_instruction_sequence::{instr, Result};
19 use hhbc_by_ref_options::{HhvmFlags, Options};
20 use naming_special_names_rust::{classes, special_idents, user_attributes};
21 use ocamlrep::rc::RcOc;
22 use oxidized::{ast as T, ast_defs};
24 use itertools::Either;
27 pub fn from_asts<'a, 'arena>(
28 alloc: &'arena bumpalo::Bump,
29 emitter: &mut Emitter<'arena>,
31 methods: &'a [T::Method_],
32 ) -> Result<Vec<HhasMethod<'arena>>> {
35 .map(|m| from_ast(alloc, emitter, class, m))
36 .collect::<Result<Vec<_>>>()
39 pub fn from_ast<'a, 'arena>(
40 alloc: &'arena bumpalo::Bump,
41 emitter: &mut Emitter<'arena>,
43 method_: impl Into<Cow<'a, T::Method_>>,
44 ) -> Result<HhasMethod<'arena>> {
45 let method_: Cow<'a, T::Method_> = method_.into();
46 let method = method_.as_ref();
47 let is_memoize = method
50 .any(|ua| user_attributes::is_memoized(&ua.name.1));
51 let class_name = string_utils::mangle(string_utils::strip_global_ns(&class.name.1).into());
52 let is_closure_body = &method.name.1 == "__invoke" && (class.name.1).starts_with("Closure$");
54 emit_attribute::from_asts(alloc, emitter, &class.namespace, &method.user_attributes)?;
56 attributes.extend(emit_attribute::add_reified_attribute(&method.tparams[..]));
58 let call_context = if is_closure_body {
59 match &method.user_attributes[..] {
61 name: ast_defs::Id(_, ref s),
63 }] if s.eq_ignore_ascii_case("__DynamicMethCallerForce") => {
64 Some("__SystemLib\\DynamicContextOverrideUnsafe".to_string())
72 let is_native = attributes
74 .any(|attr| attr.is(user_attributes::is_native));
75 let is_native_opcode_impl = hhas_attribute::is_native_opcode_impl(&attributes);
76 let is_abstract = class.kind.is_cinterface() || method.abstract_;
77 let is_async = method.fun_kind.is_fasync() || method.fun_kind.is_fasync_generator();
78 let is_no_injection = hhas_attribute::is_no_injection(&attributes);
80 if !(method.static_ || is_closure_body) {
81 for p in method.params.iter() {
82 if p.name == special_idents::THIS {
83 return Err(raise_fatal_parse(&p.pos, "Cannot re-assign $this"));
87 if class.kind.is_cinterface() && !method.body.ast.is_empty() {
88 return Err(raise_fatal_parse(
91 "Interface method {}::{} cannot contain body",
92 class_name, &method.name.1
96 if !method.static_ && class.final_ && class.kind.is_cabstract() {
97 return Err(raise_fatal_parse(
100 "Class {} contains non-static method {} and therefore cannot be declared 'abstract final'",
101 class_name, &method.name.1
106 let visibility = if is_native_opcode_impl {
107 T::Visibility::Public
108 } else if is_memoize {
109 T::Visibility::Private
113 let deprecation_info = if is_memoize {
116 hhas_attribute::deprecation_info(attributes.iter())
118 let default_dropthrough = if method.abstract_ {
119 Some(emit_fatal_runtimeomitframe(
123 "Cannot call abstract method {}::{}()",
124 class_name, &method.name.1
130 let mut scope = Scope {
132 ScopeItem::Class(ast_scope::Class::new_ref(class)),
133 ScopeItem::Method(match &method_ {
134 Cow::Borrowed(m) => ast_scope::Method::new_ref(&m),
135 Cow::Owned(m) => ast_scope::Method::new_rc(&m),
140 scope.items.push(ScopeItem::Lambda(Lambda {
142 coeffects: HhasCoeffects::default(),
145 let namespace = RcOc::clone(
150 .unwrap_or(&class.namespace),
152 let mut coeffects = HhasCoeffects::from_ast(&method.ctxs, &method.params);
153 if method.ctxs == None && is_closure_body {
154 let parent_coeffects = emitter
156 .get_lambda_coeffects_of_scope(&class.name.1, &method.name.1);
157 coeffects = parent_coeffects.inherit_to_child_closure()
159 if is_native_opcode_impl
160 && (class.name.1.as_str() == classes::GENERATOR
161 || class.name.1.as_str() == classes::ASYNC_GENERATOR)
163 match method.name.1.as_str() {
164 "send" | "raise" | "throw" | "next" | "rewind" => {
165 coeffects = coeffects.with_gen_coeffect()
170 let (ast_body_block, is_rx_body, rx_disabled) = if !coeffects.is_any_rx() {
171 (&method.body.ast, false, false)
173 match hhbc_by_ref_hhas_coeffects::halves_of_is_enabled_body(&method.body) {
174 None => (&method.body.ast, true, false),
175 Some((enabled_body, disabled_body)) => {
180 .contains(hhbc_by_ref_options::HhvmFlags::RX_IS_ENABLED)
182 (enabled_body, true, false)
184 (disabled_body, false, true)
189 let (body, is_generator, is_pair_generator) = if is_native_opcode_impl {
191 emit_native_opcode::emit_body(
196 &class.user_attributes,
199 method.ret.1.as_ref(),
205 let class_tparam_names = class
208 .map(|tp| (tp.name).1.as_str())
209 .collect::<Vec<_>>();
210 let mut flags = emit_body::Flags::empty();
212 emit_body::Flags::SKIP_AWAITABLE,
213 method.fun_kind.is_fasync(),
215 flags.set(emit_body::Flags::MEMOIZE, is_memoize);
216 flags.set(emit_body::Flags::CLOSURE_BODY, is_closure_body);
217 flags.set(emit_body::Flags::NATIVE, is_native);
218 flags.set(emit_body::Flags::RX_BODY, is_rx_body);
219 flags.set(emit_body::Flags::ASYNC, is_async);
221 emit_body::Flags::HAS_COEFFECTS_LOCAL,
222 coeffects.has_coeffects_local(),
224 emit_body::emit_body(
228 Either::Right(ast_body_block),
232 immediate_tparams: &method.tparams,
233 class_tparam_names: class_tparam_names.as_slice(),
234 ast_params: &method.params,
235 ret: method.ret.1.as_ref(),
237 deprecation_info: &deprecation_info,
238 doc_comment: method.doc_comment.clone(),
247 method::Type::from_ast_name_and_suffix(
250 emit_memoize_helpers::MEMOIZE_SUFFIX,
253 method::Type::from_ast_name(alloc, &method.name.1)
256 let is_interceptable = is_method_interceptable(emitter.options());
257 let span = if is_native_opcode_impl {
260 Span::from_pos(&method.span)
262 let mut flags = HhasMethodFlags::empty();
263 flags.set(HhasMethodFlags::IS_STATIC, method.static_);
264 flags.set(HhasMethodFlags::IS_FINAL, method.final_);
265 flags.set(HhasMethodFlags::IS_ABSTRACT, is_abstract);
266 flags.set(HhasMethodFlags::IS_ASYNC, is_async);
267 flags.set(HhasMethodFlags::IS_GENERATOR, is_generator);
268 flags.set(HhasMethodFlags::IS_PAIR_GENERATOR, is_pair_generator);
269 flags.set(HhasMethodFlags::IS_CLOSURE_BODY, is_closure_body);
270 flags.set(HhasMethodFlags::IS_INTERCEPTABLE, is_interceptable);
271 flags.set(HhasMethodFlags::IS_MEMOIZE_IMPL, is_memoize);
272 flags.set(HhasMethodFlags::RX_DISABLED, rx_disabled);
273 flags.set(HhasMethodFlags::NO_INJECTION, is_no_injection);
285 fn is_method_interceptable(opts: &Options) -> bool {
288 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)