Skip to content

Commit

Permalink
Begin AST validation logic
Browse files Browse the repository at this point in the history
Summary:
Set up some new syntax in the NodeKind macro to allow specifying
parents of Node kinds, as well as constraints on each of their children.

A new `validate` function handles validating a Node and its children.
It returns a `bool` to indicate the failure to validate.
This can be used in conjunction with a visitor to recursively validate
an entire AST.

Reviewed By: tmikov

Differential Revision: D30081493

fbshipit-source-id: d9a5c6beb14b07589e5977a96e3aef3baf582e09
  • Loading branch information
avp authored and facebook-github-bot committed Aug 13, 2021
1 parent 6837c26 commit 55dbb01
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 12 deletions.
84 changes: 72 additions & 12 deletions unsupported/juno/src/ast/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use super::{Node, NodeChild, NodeLabel, NodeList, NodePtr, StringLiteral, Visito
/// ```
/// gen_nodekind_enum! {
/// NodeKind {
/// Node1 {
/// field1: type1,
/// Node1[parent] {
/// field1: type1[constraint_a, constraint_b],
/// field2: type2,
/// },
/// Node2,
Expand All @@ -22,11 +22,19 @@ use super::{Node, NodeChild, NodeLabel, NodeList, NodePtr, StringLiteral, Visito
/// ```
/// and creates the necessary enum and supporting function implementations
/// to go along with it in order to avoid excessive copy/paste boilerplate.
/// Parents and constraints can be any member of `NodeVariant`,
/// which includes any constructible `NodeKind`s` as well as interfaces like
/// `Statement`, `Expression`, etc.
/// If multiple constraints are provided, at least one must be satisfied.
/// The `null` constraint is encoded via `Option`, it need not be listed explicitly.
macro_rules! gen_nodekind_enum {
($name:ident {
$(
$kind:ident $({
$($field:ident : $type:ty),*
$kind:ident $([ $parent:ident ])? $({
$(
$field:ident : $type:ty
$( [ $( $constraint:ident ),* ] )?
),*
$(,)?
})?
),*
Expand Down Expand Up @@ -67,6 +75,29 @@ macro_rules! gen_nodekind_enum {
}
}

pub fn variant(&self) -> NodeVariant {
match self {
$(
Self::$kind { .. } => NodeVariant::$kind
),*
}
}

/// Check whether this is a valid kind for `node`.
pub fn validate<'n>(&self, node: &'n Node) -> bool {
match self {
$(
Self::$kind $({$($field),*})? => {
// Run the validation for each child.
// Use `true &&` to make it work when there's no children.
true $(&& $(
$field.validate(node, &[$($(NodeVariant::$constraint),*)?])
)&&*)?
}
),*
}
}

pub fn name(&self) -> &'static str {
match self {
$(
Expand All @@ -77,6 +108,35 @@ macro_rules! gen_nodekind_enum {
}
}
}

/// Just type information on the node without any of the children.
/// Used for performing tasks based only on the type of the AST node
/// without having to know more about it.
/// Includes "abstract" nodes which cannot be truly constructed.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum NodeVariant {
Expression,
Statement,
Literal,
$($kind),*
}

impl NodeVariant {
/// The `parent` of the variant in ESTree, used for validation.
/// Return `None` if there is no parent.
pub fn parent(&self) -> Option<NodeVariant> {
match self {
Self::Expression => None,
Self::Statement => None,
Self::Literal => Some(Self::Expression),
$(
Self::$kind => {
None$(.or(Some(Self::$parent)))?
}
),*
}
}
}
};
}

Expand Down Expand Up @@ -156,8 +216,8 @@ NodeKind {
ThrowStatement {
argument: NodePtr,
},
ReturnStatement {
argument: Option<NodePtr>,
ReturnStatement[Statement] {
argument: Option<NodePtr>[Expression],
},
WithStatement {
object: NodePtr,
Expand Down Expand Up @@ -185,21 +245,21 @@ NodeKind {
consequent: NodePtr,
alternate: Option<NodePtr>,
},
NullLiteral,
BooleanLiteral {
NullLiteral[Literal],
BooleanLiteral[Literal] {
value: bool,
},
StringLiteral {
StringLiteral[Literal] {
value: StringLiteral,
},
NumericLiteral {
NumericLiteral[Literal] {
value: f64,
},
RegExpLiteral {
RegExpLiteral[Literal] {
pattern: NodeLabel,
flags: NodeLabel,
},
ThisExpression,
ThisExpression[Expression],
Super,
SequenceExpression {
expressions: NodeList,
Expand Down
72 changes: 72 additions & 0 deletions unsupported/juno/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::fmt;
mod kind;

pub use kind::NodeKind;
use kind::NodeVariant;

/// A JavaScript AST node.
#[derive(Debug)]
Expand All @@ -27,6 +28,11 @@ impl Node {
visitor.call(self, parent);
}

/// Check whether the node is valid.
pub fn validate(&self) -> bool {
self.kind.validate(self)
}

/// Call the `visitor` on only this node's children.
pub fn visit_children<V: Visitor>(&self, visitor: &mut V) {
self.kind.visit_children(visitor, self);
Expand Down Expand Up @@ -96,22 +102,41 @@ trait NodeChild {
/// Should be no-op for any type that doesn't contain pointers to other
/// `Node`s.
fn visit<V: Visitor>(&self, visitor: &mut V, node: &Node);

/// Check whether this is a valid child of `node` given the constraints.
fn validate(&self, node: &Node, constraints: &[NodeVariant]) -> bool;
}

impl NodeChild for f64 {
fn visit<V: Visitor>(&self, _visitor: &mut V, _node: &Node) {}

fn validate(&self, _node: &Node, _constraints: &[NodeVariant]) -> bool {
true
}
}

impl NodeChild for bool {
fn visit<V: Visitor>(&self, _visitor: &mut V, _node: &Node) {}

fn validate(&self, _node: &Node, _constraints: &[NodeVariant]) -> bool {
true
}
}

impl NodeChild for NodeLabel {
fn visit<V: Visitor>(&self, _visitor: &mut V, _node: &Node) {}

fn validate(&self, _node: &Node, _constraints: &[NodeVariant]) -> bool {
true
}
}

impl NodeChild for StringLiteral {
fn visit<V: Visitor>(&self, _visitor: &mut V, _node: &Node) {}

fn validate(&self, _node: &Node, _constraints: &[NodeVariant]) -> bool {
true
}
}

impl<T: NodeChild> NodeChild for Option<T> {
Expand All @@ -120,12 +145,28 @@ impl<T: NodeChild> NodeChild for Option<T> {
t.visit(visitor, node);
}
}

fn validate(&self, node: &Node, constraints: &[NodeVariant]) -> bool {
match self {
None => true,
Some(t) => t.validate(node, constraints),
}
}
}

impl NodeChild for NodePtr {
fn visit<V: Visitor>(&self, visitor: &mut V, node: &Node) {
visitor.call(self, Some(node));
}

fn validate(&self, _node: &Node, constraints: &[NodeVariant]) -> bool {
for &constraint in constraints {
if instanceof(self.kind.variant(), constraint) {
return true;
}
}
false
}
}

impl NodeChild for NodeList {
Expand All @@ -134,6 +175,37 @@ impl NodeChild for NodeList {
visitor.call(child, Some(node));
}
}

fn validate(&self, _node: &Node, constraints: &[NodeVariant]) -> bool {
'elems: for elem in self {
for &constraint in constraints {
if instanceof(elem.kind.variant(), constraint) {
// Found a valid constraint for this element,
// move on to the next element.
continue 'elems;
}
}
// Failed to find a constraint that matched, early return.
return false;
}
true
}
}

/// Return whether `subtype` contains `supertype` in its parent chain.
fn instanceof(subtype: NodeVariant, supertype: NodeVariant) -> bool {
let mut cur = subtype;
loop {
if cur == supertype {
return true;
}
match cur.parent() {
None => return false,
Some(next) => {
cur = next;
}
}
}
}

#[cfg(test)]
Expand Down
30 changes: 30 additions & 0 deletions unsupported/juno/tests/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@

use juno::ast::{Node, NodeKind, NodePtr, SourceLoc, SourceRange, Visitor};

pub fn node(kind: NodeKind) -> Box<Node> {
let range = SourceRange {
file: 0,
start: SourceLoc { line: 0, col: 0 },
end: SourceLoc { line: 0, col: 0 },
};

Box::new(Node { range, kind })
}

#[test]
fn test_visit() {
use NodeKind::*;
Expand Down Expand Up @@ -63,3 +73,23 @@ fn test_visit() {
ast.visit(&mut visitor, None);
assert_eq!(visitor.acc, [1.0, 2.0]);
}

#[test]
fn test_valid() {
use NodeKind::*;
assert!(node(ReturnStatement { argument: None }).validate());

assert!(
node(ReturnStatement {
argument: Some(node(NumericLiteral { value: 1.0 }))
})
.validate()
);

assert!(
!node(ReturnStatement {
argument: Some(node(ReturnStatement { argument: None })),
})
.validate()
);
}

0 comments on commit 55dbb01

Please sign in to comment.