use hermes_ast::context::GCLock;
use hermes_ast::node::{builder, AssignmentExpression, Node, NodeField};
use hermes_ast::visitor::{Path, TransformResult, VisitorMut};
use crate::ast_eval::{
ast_fold_binary_expression, ast_fold_unary_expression,
};
use crate::linearize::{
linearize_left, linearize_right, OperatorExpr, MAX_NESTED_ASSIGNMENTS,
MAX_NESTED_BINARY,
};
use crate::sem_context::{Atom, Constness, DeclKind, DeclSpecial};
use super::declarations::atom_str;
use super::functions::is_generator;
use super::SemanticResolver;
const CODE_GENERATION_SETTINGS_TEST262: bool = false;
fn rebuild_assignment_chain<'gc>(
gc: &'gc GCLock,
list: &[&'gc AssignmentExpression<'gc>],
left_repl: &[Option<&'gc Node<'gc>>],
right_repl: Option<&'gc Node<'gc>>,
) -> TransformResult<&'gc Node<'gc>> {
debug_assert_eq!(list.len(), left_repl.len());
let mut child = right_repl;
for i in (0..list.len()).rev() {
let mut b = builder::AssignmentExpression::from_node(list[i]);
if let Some(v) = left_repl[i] {
b.left(v);
}
if let Some(v) = child {
b.right(v);
}
child = match b.build(gc) {
TransformResult::Changed(v) => Some(v),
TransformResult::Unchanged => None,
other => unreachable!("a builder never yields {other:?}"),
};
}
match child {
Some(v) => TransformResult::Changed(v),
None => TransformResult::Unchanged,
}
}
pub(super) fn replacement_of<'gc>(
result: TransformResult<&'gc Node<'gc>>,
) -> Option<&'gc Node<'gc>> {
match result {
TransformResult::Changed(v) => Some(v),
TransformResult::Unchanged => None,
other => unreachable!(
"the resolver never removes or expands a child: {other:?}"
),
}
}
impl SemanticResolver<'_, '_, '_, '_> {
pub(super) fn visit_binary_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let be = node
.as_binary_expression()
.expect("visit_binary_expression: not a BinaryExpression");
let ops = [self.kw().ident_plus, self.kw().ident_minus];
if be.operator.get() == ops[0] || be.operator.get() == ops[1] {
let list = linearize_left(be, &ops);
if list.len() > MAX_NESTED_BINARY as usize {
self.recursion_depth_exceeded(node);
return TransformResult::Unchanged;
}
let node_of = |i: usize| -> &'gc Node<'gc> {
if i + 1 < list.len() {
list[i + 1].left()
} else {
node
}
};
let left0 = replacement_of(self.call(
gc,
list[0].left(),
Some(Path::new(node_of(0), NodeField::left)),
));
let mut right_repl: Vec<Option<&'gc Node<'gc>>> =
Vec::with_capacity(list.len());
for (i, e) in list.iter().enumerate() {
right_repl.push(replacement_of(self.call(
gc,
e.right(),
Some(Path::new(node_of(i), NodeField::right)),
)));
}
let mut replacement = left0;
let mut folding = self.compile();
for i in 0..list.len() {
let mut b = builder::BinaryExpression::from_node(list[i]);
if let Some(v) = replacement {
b.left(v);
}
if let Some(v) = right_repl[i] {
b.right(v);
}
let (cur, cur_changed) = match b.build(gc) {
TransformResult::Changed(v) => (v, true),
TransformResult::Unchanged => (node_of(i), false),
other => unreachable!("a builder never yields {other:?}"),
};
if folding {
let cur_be = cur.as_binary_expression().expect(
"a rebuilt BinaryExpression is a BinaryExpression",
);
if let Some(folded) =
ast_fold_binary_expression(gc, self.kw(), cur_be)
{
replacement = Some(folded);
continue;
}
folding = false;
}
replacement = if cur_changed { Some(cur) } else { None };
}
return match replacement {
Some(v) => TransformResult::Changed(v),
None => TransformResult::Unchanged,
};
}
let result = node.visit_children_mut(gc, self);
if !self.compile() {
return result;
}
let cur = match result {
TransformResult::Changed(v) => v,
TransformResult::Unchanged => node,
ref other => unreachable!(
"the resolver never removes or expands a child: {other:?}"
),
};
let cur_be = cur
.as_binary_expression()
.expect("a rebuilt BinaryExpression is a BinaryExpression");
match ast_fold_binary_expression(gc, self.kw(), cur_be) {
Some(folded) => TransformResult::Changed(folded),
None => result,
}
}
pub(super) fn visit_assignment_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let assignment = node
.as_assignment_expression()
.expect("visit_assignment_expression: not an AssignmentExpression");
let ops = [self.kw().ident_assign];
if assignment.operator.get() == ops[0] {
let list = linearize_right(assignment, &ops);
if list.len() > MAX_NESTED_ASSIGNMENTS as usize {
self.recursion_depth_exceeded(node);
return TransformResult::Unchanged;
}
let node_of = |i: usize| -> &'gc Node<'gc> {
if i == 0 {
node
} else {
list[i - 1].right()
}
};
let mut left_repl: Vec<Option<&'gc Node<'gc>>> =
vec![None; list.len()];
for i in 0..list.len() {
let e = list[i];
let left_node = match self.call(
gc,
e.left(),
Some(Path::new(node_of(i), NodeField::left)),
) {
TransformResult::Changed(v) => {
left_repl[i] = Some(v);
v
}
TransformResult::Unchanged => e.left(),
other => unreachable!(
"the resolver never removes or expands a child: {other:?}"
),
};
if self.recursion_depth == 0 {
return rebuild_assignment_chain(
gc, &list, &left_repl, None,
);
}
self.validate_assignment_target(left_node);
}
let last = list.len() - 1;
let right_repl = replacement_of(self.call(
gc,
list[last].right(),
Some(Path::new(node_of(last), NodeField::right)),
));
return rebuild_assignment_chain(
gc, &list, &left_repl, right_repl,
);
}
let mut b = builder::AssignmentExpression::from_node(assignment);
let left_node = match self.call(
gc,
assignment.left,
Some(Path::new(node, NodeField::left)),
) {
TransformResult::Changed(v) => {
b.left(v);
v
}
TransformResult::Unchanged => assignment.left,
other => unreachable!(
"the resolver never removes or expands a child: {other:?}"
),
};
if self.recursion_depth == 0 {
return b.build(gc);
}
self.validate_assignment_target(left_node);
if let TransformResult::Changed(v) = self.call(
gc,
assignment.right,
Some(Path::new(node, NodeField::right)),
) {
b.right(v);
}
b.build(gc)
}
pub(super) fn visit_update_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let result = node.visit_children_mut(gc, self);
if self.recursion_depth == 0 {
return result;
}
let cur = match result {
TransformResult::Changed(v) => v,
TransformResult::Unchanged => node,
ref other => unreachable!(
"the resolver never removes or expands a child: {other:?}"
),
};
let argument = cur
.as_update_expression()
.expect("a rebuilt UpdateExpression is an UpdateExpression")
.argument;
if !self.is_lvalue(argument) {
self.sm.error_range(
argument.range(),
"invalid operand in update operation",
);
}
result
}
pub(super) fn visit_unary_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let ue = node
.as_unary_expression()
.expect("visit_unary_expression: not a UnaryExpression");
if ue.operator.get() == self.kw().ident_delete {
if self.sem_ctx.function(self.cur_function_info()).strict
&& matches!(ue.argument, Node::Identifier(_))
{
self.sm.error_range(
node.range(),
"'delete' of a variable is not allowed in strict mode",
);
}
if !CODE_GENERATION_SETTINGS_TEST262 {
if let Node::MemberExpression(mem) = ue.argument {
if matches!(mem.object, Node::Super(_)) {
self.sm.error_range(
node.range(),
"'delete' of super property is not allowed",
);
}
}
}
}
let result = node.visit_children_mut(gc, self);
if !self.compile() {
return result;
}
let cur = match result {
TransformResult::Changed(v) => v,
TransformResult::Unchanged => node,
ref other => unreachable!(
"the resolver never removes or expands a child: {other:?}"
),
};
let cur_ue = cur
.as_unary_expression()
.expect("a rebuilt UnaryExpression is a UnaryExpression");
match ast_fold_unary_expression(gc, self.kw(), cur_ue) {
Some(folded) => TransformResult::Changed(folded),
None => result,
}
}
pub(super) fn validate_assignment_target(&mut self, node: &Node) {
if matches!(node, Node::Empty(_)) {
return;
}
if let Node::AssignmentPattern(assign) = node {
self.validate_assignment_target(assign.left);
return;
}
if let Node::Property(prop) = node {
self.validate_assignment_target(prop.value);
return;
}
if let Node::ArrayPattern(arr) = node {
for elem in arr.elements.iter() {
self.validate_assignment_target(elem);
}
return;
}
if let Node::ObjectPattern(obj) = node {
for prop_node in obj.properties.iter() {
self.validate_assignment_target(prop_node);
}
return;
}
if let Node::RestElement(rest) = node {
self.validate_assignment_target(rest.argument);
return;
}
if !self.is_lvalue(node) {
self.sm.error_range(
node.range(),
"invalid assignment left-hand side",
);
}
}
pub(super) fn is_lvalue(&self, node: &Node) -> bool {
if matches!(node, Node::MemberExpression(_)) {
return true;
}
if let Node::Identifier(id) = node {
let decl = self
.sem_ctx
.get_expression_decl(id)
.expect("Identifier must be resolved");
if !CODE_GENERATION_SETTINGS_TEST262 {
let constness = self.sem_ctx.decl(decl).kind.constness();
if constness == Constness::Always
|| (self
.sem_ctx
.function(self.cur_function_info())
.strict
&& constness == Constness::StrictModeOnly)
{
return false;
}
}
if self.sem_ctx.function(self.cur_function_info()).strict {
if id.name.get() == self.kw().ident_arguments
|| id.name.get() == self.kw().ident_eval
{
return false;
}
} else {
if self.sem_ctx.decl(decl).special == DeclSpecial::Arguments {
return false;
}
}
return true;
}
false
}
pub(super) fn visit_yield_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let in_non_generator = match &self.function_context().node {
Some(n) => !is_generator(n.node(gc)),
None => false,
};
if self.in_global_scope_context() || in_non_generator {
self.sm.error_range(
node.range(),
"'yield' not in a generator function",
);
}
if self.function_context().is_formal_params {
self.sm.error_range(
node.range(),
"'yield' not allowed in a formal parameter",
);
}
node.visit_children_mut(gc, self)
}
pub(super) fn visit_await_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
if self.forbid_await_expression {
self.sm
.error_range(node.range(), "'await' not in an async function");
}
if self.function_context().is_formal_params {
self.sm.error_range(
node.range(),
"'await' not allowed in a formal parameter",
);
}
node.visit_children_mut(gc, self)
}
pub(super) fn visit_spread_element<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
path: Option<Path<'gc>>,
) -> TransformResult<&'gc Node<'gc>> {
if !matches!(
path.map(|p| p.parent),
Some(
Node::ObjectExpression(_)
| Node::ArrayExpression(_)
| Node::CallExpression(_)
| Node::OptionalCallExpression(_)
| Node::NewExpression(_)
| Node::RecordExpressionProperties(_)
)
) {
self.sm
.error_range(node.range(), "spread operator is not supported");
}
node.visit_children_mut(gc, self)
}
pub(super) fn visit_meta_property<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let mp = node
.as_meta_property()
.expect("visit_meta_property: not a MetaProperty");
let meta = meta_property_name(mp.meta);
let property = meta_property_name(mp.property);
if meta == self.kw().ident_new && property == self.kw().ident_target {
if self.in_global_scope_context() {
self.sm.error_range(
node.range(),
"'new.target' not in a function",
);
}
let sem_info = self.function_context().sem_info;
if self.sem_ctx.nearest_non_arrow(sem_info)
== self.sem_ctx.get_global_function()
{
self.sm.error_range(
node.range(),
"'new.target' not allowed in arrow function in global \
scope",
);
}
return TransformResult::Unchanged;
}
if meta == self.kw().ident_import && property == self.kw().ident_meta {
if self.compile() {
self.sm.error_range(
node.range(),
"'import.meta' is currently unsupported",
);
}
return TransformResult::Unchanged;
}
self.sm.error_range(
node.range(),
format!(
"invalid meta property {}.{}",
atom_str(gc, meta),
atom_str(gc, property)
),
);
TransformResult::Unchanged
}
pub(super) fn visit_cover_node<'gc>(
&mut self,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
match node {
Node::CoverEmptyArgs(_) => {
self.sm.error_range(
node.range(),
"invalid empty parentheses '( )'",
);
}
Node::CoverTrailingComma(_) => {
self.sm
.error_range(node.range(), "expression expected after ','");
}
Node::CoverInitializer(_) => {
self.sm.error(
node.range().start,
"':' expected in property initialization",
);
}
Node::CoverRestElement(_) => {
self.sm.error_range(
node.range(),
"'...' not allowed in this context",
);
}
Node::CoverTypedIdentifier(_) => {
self.sm.error_range(
node.range(),
"typecast not allowed in this context",
);
}
_ => unreachable!(
"visit_cover_node on a {}",
node.node_type_str()
),
}
TransformResult::Unchanged
}
pub(super) fn visit_type_cast_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let n = node.as_type_cast_expression().expect(
"visit_type_cast_expression: not a TypeCastExpression",
);
let mut b = builder::TypeCastExpression::from_node(n);
if let TransformResult::Changed(v) = self.call(
gc,
n.expression,
Some(Path::new(node, NodeField::expression)),
) {
b.expression(v);
}
b.build(gc)
}
pub(super) fn visit_as_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let n = node
.as_as_expression()
.expect("visit_as_expression: not an AsExpression");
let mut b = builder::AsExpression::from_node(n);
if let TransformResult::Changed(v) = self.call(
gc,
n.expression,
Some(Path::new(node, NodeField::expression)),
) {
b.expression(v);
}
b.build(gc)
}
pub(super) fn visit_match_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
if self.compile() {
self.sm
.error_range(node.range(), "match expressions are unsupported");
}
node.visit_children_mut(gc, self)
}
pub(super) fn visit_regexp_literal<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
) -> TransformResult<&'gc Node<'gc>> {
let regexp = node
.as_reg_exp_literal()
.expect("visit_regexp_literal: not a RegExpLiteral");
if self.compile() {
match try_compile_regexp(regexp.pattern.get(), regexp.flags.get()) {
Ok(()) => {
}
Err(regexp_error) => panic!(
"sema: REGEX-ENGINE DEFERRED — the stub validator \
rejected /{}/{}: {regexp_error}. Replacing it with a \
real validator means porting \
SemanticResolver.cpp:829-832's diagnostic at the same \
time: sm_.error(regexp->getSourceRange(), \"Invalid \
regular expression: \" + Twine(regexpError))",
atom_str(gc, regexp.pattern.get()),
atom_str(gc, regexp.flags.get()),
),
}
}
node.visit_children_mut(gc, self)
}
pub(super) fn visit_member_like_expression<'gc>(
&mut self,
gc: &'gc GCLock,
node: &'gc Node<'gc>,
path: Option<Path<'gc>>,
) -> TransformResult<&'gc Node<'gc>> {
let (object, property, optional) = match node {
Node::MemberExpression(m) => (m.object, m.property, false),
Node::OptionalMemberExpression(m) => (m.object, m.property, true),
_ => unreachable!(
"visit_member_like_expression on a {}",
node.node_type_str()
),
};
if let Node::PrivateName(name) = property {
if !optional && matches!(object, Node::Super(_)) {
self.sm.error_range(
node.range(),
"Cannot lookup private names on super.",
);
}
let delete_parent = path.filter(|p| {
matches!(p.parent, Node::UnaryExpression(op)
if op.operator.get() == self.kw().ident_delete)
});
if let Some(p) = delete_parent {
let range = if optional {
p.parent.range()
} else {
node.range()
};
self.sm
.error_range(range, "Cannot `delete` with a private name.");
}
if !CODE_GENERATION_SETTINGS_TEST262 {
let decl = self.resolve_private_name(gc, name.id);
if let Some(decl) = decl {
let assign_to_this = path.filter(|p| {
matches!(p.parent, Node::AssignmentExpression(_))
&& p.field == NodeField::left
});
if let Some(p) = assign_to_this {
let kind = self.sem_ctx.decl(decl).kind;
if kind == DeclKind::PrivateGetter {
self.sm.error_range(
p.parent.range(),
"Cannot store to a private name that only \
defines a getter.",
);
} else if kind == DeclKind::PrivateMethod {
self.sm.error_range(
p.parent.range(),
"Cannot store to a private name that defines \
a method.",
);
}
} else {
if self.sem_ctx.decl(decl).kind
== DeclKind::PrivateSetter
{
self.sm.error_range(
node.range(),
"Cannot load from a private name that only \
defines a setter.",
);
}
}
}
}
}
node.visit_children_mut(gc, self)
}
}
fn try_compile_regexp(_pattern: Atom, _flags: Atom) -> Result<(), String> {
Ok(())
}
fn meta_property_name(node: &Node) -> Atom {
match node {
Node::Identifier(id) => id.name.get(),
_ => panic!(
"MetaProperty child is a {}, not an Identifier",
node.node_type_str()
),
}
}
#[cfg(test)]
mod tests {
use hermes_ast::context::Context;
use hermes_ast::node::{Identifier, NumericLiteral, Program};
use hermes_ast::node_child::{NodeList, NodeMetadata};
use hermes_support::location::{SMLoc, SMRange};
use hermes_support::manager::SourceErrorManager;
use hermes_support::persistent_scoped_map::Scope;
use super::*;
use crate::keywords::Keywords;
use crate::sem_context::{
Binding, ConstructorKind, CustomDirectives, DeclKind, SemContext,
};
fn check_identifier_target(
name: &str,
kind: DeclKind,
special: DeclSpecial,
strict: bool,
) -> (bool, usize) {
let mut ctx = Context::new();
let mut sm = SourceErrorManager::new();
let buf = sm.add_buffer_bytes("lv.js", b"x");
let loc = SMLoc {
source: buf,
offset: 0,
};
let range = SMRange {
start: loc,
end: loc,
};
let gc = ctx.lock();
let mut sem_ctx = SemContext::new(Keywords::new(&gc));
let atom = gc.atom_bytes(name);
let ident_node = gc.alloc(Node::Identifier(Identifier::new(
NodeMetadata::new(range),
atom,
None,
false,
)));
let program = gc.alloc(Node::Program(Program::new(
NodeMetadata::new(range),
NodeList::from_iter(&gc, []),
)));
let binding_table = sem_ctx.binding_table_rc();
let is_lv = {
let mut resolver = SemanticResolver::new(
&binding_table,
&mut sem_ctx,
&mut sm,
&[],
true,
);
let func_state = resolver.enter_function(
&gc,
program,
None,
strict,
ConstructorKind::None,
CustomDirectives::default(),
true,
);
let scope_state = resolver.enter_scope(None, true);
let scope = resolver.cur_scope.expect("scope just entered");
let decl = resolver
.sem_ctx
.new_decl_in_scope(atom, kind, scope, special);
resolver
.binding_table
.try_emplace(atom, Binding::new(decl, None));
let ident = ident_node
.as_identifier()
.expect("just built an Identifier");
resolver.sem_ctx.set_expression_decl(
ident_node.node_id(),
ident,
Some(decl),
);
let is_lv = resolver.is_lvalue(ident_node);
resolver.validate_assignment_target(ident_node);
resolver.exit_scope(scope_state);
resolver.exit_function(func_state);
is_lv
};
let errors = sm.error_count() as usize;
(is_lv, errors)
}
#[test]
fn is_lvalue_accepts_a_mutable_binding() {
let (is_lv, errors) = check_identifier_target(
"x",
DeclKind::Let,
DeclSpecial::NotSpecial,
false,
);
assert!(is_lv);
assert_eq!(errors, 0);
}
#[test]
fn is_lvalue_rejects_const_in_both_modes() {
for strict in [false, true] {
let (is_lv, errors) = check_identifier_target(
"c",
DeclKind::Const,
DeclSpecial::NotSpecial,
strict,
);
assert!(!is_lv, "const must not be an lvalue (strict={strict})");
assert_eq!(errors, 1);
}
}
#[test]
fn is_lvalue_rejects_strict_mode_only_constness_only_when_strict() {
let (loose, loose_errs) = check_identifier_target(
"f",
DeclKind::FunctionExprName,
DeclSpecial::NotSpecial,
false,
);
assert!(loose, "a function expression name is writable in loose mode");
assert_eq!(loose_errs, 0);
let (strict, strict_errs) = check_identifier_target(
"f",
DeclKind::FunctionExprName,
DeclSpecial::NotSpecial,
true,
);
assert!(!strict);
assert_eq!(strict_errs, 1);
}
#[test]
fn is_lvalue_rejects_eval_and_arguments_by_name_in_strict_mode() {
for name in ["eval", "arguments"] {
let (is_lv, errors) = check_identifier_target(
name,
DeclKind::Let,
DeclSpecial::NotSpecial,
true,
);
assert!(!is_lv, "strict mode must reject `{name}` as a target");
assert_eq!(errors, 1);
let (loose, loose_errs) = check_identifier_target(
name,
DeclKind::Let,
DeclSpecial::NotSpecial,
false,
);
assert!(loose, "loose mode must accept `{name}` bound to a let");
assert_eq!(loose_errs, 0);
}
}
#[test]
fn is_lvalue_rejects_the_special_arguments_decl_in_loose_mode() {
let (is_lv, errors) = check_identifier_target(
"arguments",
DeclKind::Var,
DeclSpecial::Arguments,
false,
);
assert!(!is_lv);
assert_eq!(errors, 1);
}
#[test]
fn is_lvalue_rejects_a_literal() {
let mut ctx = Context::new();
let mut sm = SourceErrorManager::new();
let buf = sm.add_buffer_bytes("lv.js", b"1");
let loc = SMLoc {
source: buf,
offset: 0,
};
let range = SMRange {
start: loc,
end: loc,
};
let gc = ctx.lock();
let mut sem_ctx = SemContext::new(Keywords::new(&gc));
let lit = gc.alloc(Node::NumericLiteral(NumericLiteral::new(
NodeMetadata::new(range),
1.0,
)));
let binding_table = sem_ctx.binding_table_rc();
let _scope = Scope::new(&binding_table);
let is_lv = {
let resolver = SemanticResolver::new(
&binding_table,
&mut sem_ctx,
&mut sm,
&[],
true,
);
resolver.is_lvalue(lit)
};
assert!(!is_lv);
}
#[test]
fn constness_table_matches_the_kinds_used_above() {
assert_eq!(DeclKind::Const.constness(), Constness::Always);
assert_eq!(
DeclKind::FunctionExprName.constness(),
Constness::StrictModeOnly
);
assert_eq!(DeclKind::Let.constness(), Constness::Never);
}
}