Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.d/1504.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Added `and`(&), `or`(^), and `not`(~) bitwise operators.

author: asauzeau
5 changes: 4 additions & 1 deletion lib/fuzz/inputs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ decode_zlib!(decode_base64!("eJwNy4ENwCAIBMCNXIlQ/KqplUSgCdvXAS41qPMHshCB2R1zJlW
decode_zstd!(decode_base64!("KLUv/QBY/QEAYsQOFKClbQBedqXsb96EWDax/f/F/z+gNU4ZTInaUeAj82KqPFjUzKqhcfDqAIsLvAsnY1bI/N2mHzDixRQA"))
encode_base16("please encode me")
encode_base64("please encode me")
encode_base64(encode_gzip("please encode me"))
encode_base64(encode_gzip("please encode me"))
15 & 20
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition. Did you have a chance to run the fuzzer as well?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I tested it for about ten minutes and didn't experience any crashes. I'll try to run it longer.

15 ^ 20
~20
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# result: 5

21 & 45
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# result: 61

21 ^ 45
3 changes: 3 additions & 0 deletions lib/tests/tests/expressions/unary/bitwise_not.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# result: [-76, 40, -26]

[~75, ~~40, ~~~25]
19 changes: 17 additions & 2 deletions src/compiler/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::compiler::expression::function_call::FunctionCallError;
use crate::compiler::{
CompileConfig, Function, Program, TypeDef,
expression::{
Abort, Array, Assignment, Block, Container, Expr, Expression, FunctionArgument,
Abort, Array, Assignment, BitwiseNot, Block, Container, Expr, Expression, FunctionArgument,
FunctionCall, Group, IfStatement, Literal, Noop, Not, Object, Op, Predicate, Query, Return,
Target, Unary, Variable, assignment, function_call, literal, predicate, query,
},
Expand Down Expand Up @@ -801,10 +801,11 @@ impl<'a> Compiler<'a> {
}

fn compile_unary(&mut self, node: Node<ast::Unary>, state: &mut TypeState) -> Option<Unary> {
use ast::Unary::Not;
use ast::Unary::{BitwiseNot, Not};

let variant = match node.into_inner() {
Not(node) => self.compile_not(node, state)?.into(),
BitwiseNot(node) => self.compile_bitwise_not(node, state)?.into(),
};

Some(Unary::new(variant))
Expand All @@ -820,6 +821,20 @@ impl<'a> Compiler<'a> {
.ok()
}

fn compile_bitwise_not(
&mut self,
node: Node<ast::BitwiseNot>,
state: &mut TypeState,
) -> Option<BitwiseNot> {
let (not, expr) = node.into_inner().take();

let node = Node::new(expr.span(), self.compile_expr(*expr, state)?);

BitwiseNot::new(node, not.span(), state)
.map_err(|err| self.diagnostics.push(Box::new(err)))
.ok()
}

fn compile_abort(&mut self, node: Node<ast::Abort>, state: &mut TypeState) -> Option<Abort> {
self.abortable = true;
let (span, abort) = node.take();
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use dyn_clone::{DynClone, clone_trait_object};
pub use abort::Abort;
pub use array::Array;
pub use assignment::Assignment;
pub use bitwise_not::BitwiseNot;
pub use block::Block;
pub use container::{Container, Variant};
#[allow(clippy::module_name_repetitions)]
Expand Down Expand Up @@ -34,6 +35,7 @@ use super::{Context, TypeDef};

mod abort;
mod array;
mod bitwise_not;
mod block;
mod function_argument;
mod group;
Expand Down
160 changes: 160 additions & 0 deletions src/compiler/expression/bitwise_not.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use std::fmt;

use crate::compiler::state::{TypeInfo, TypeState};
use crate::compiler::{
Context, Expression, Span,
expression::{Expr, Resolved},
parser::Node,
value::{Kind, VrlValueArithmetic},
};
use crate::diagnostic::{DiagnosticMessage, Label, Note, Urls};

#[derive(Debug, Clone, PartialEq)]
pub struct BitwiseNot {
inner: Box<Expr>,
}

pub(crate) type Result = std::result::Result<BitwiseNot, Error>;

impl BitwiseNot {
/// Creates a new `BitwiseNot` expression.
///
/// # Errors
/// Returns an `Error` if the provided expression's type is not integer or bytes.
///
/// # Arguments
/// * `node` - The node representing the expression.
/// * `not_span` - The span of the `bitwise not` operator.
/// * `state` - The current type state.
///
/// # Returns
/// A `Result` containing the new `BitwiseNot` expression or an error.
///
/// # Errors
/// - `NonInteger`: If operand is not of type integer.
pub fn new(node: Node<Expr>, not_span: Span, state: &TypeState) -> Result {
let (expr_span, expr) = node.take();
let type_def = expr.type_info(state).result;

if !type_def.is_integer() && !type_def.is_bytes() {
return Err(Error {
variant: ErrorVariant::NonInteger(type_def.into()),
not_span,
expr_span,
});
}

Ok(Self {
inner: Box::new(expr),
})
}
}

impl Expression for BitwiseNot {
fn resolve(&self, ctx: &mut Context) -> Resolved {
Ok(self.inner.resolve(ctx)?.try_bitwise_not()?)
}

fn type_info(&self, state: &TypeState) -> TypeInfo {
let mut state = state.clone();
let mut inner_def = self.inner.apply_type_info(&mut state);
if inner_def.is_integer() {
inner_def = inner_def.infallible().with_kind(Kind::integer());
} else {
inner_def = inner_def.fallible().with_kind(Kind::integer());
}
TypeInfo::new(state, inner_def)
}
}

impl fmt::Display for BitwiseNot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "~{}", self.inner)
}
}

// -----------------------------------------------------------------------------

#[derive(Debug)]
pub struct Error {
pub(crate) variant: ErrorVariant,

not_span: Span,
expr_span: Span,
}

#[derive(thiserror::Error, Debug)]
pub(crate) enum ErrorVariant {
#[error("non-integer bitwise negation")]
NonInteger(Kind),
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:#}", self.variant)
}
}

impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.variant)
}
}

impl DiagnosticMessage for Error {
fn code(&self) -> usize {
use ErrorVariant::NonInteger;

match &self.variant {
NonInteger(..) => 670,
}
}

fn labels(&self) -> Vec<Label> {
use ErrorVariant::NonInteger;

match &self.variant {
NonInteger(kind) => vec![
Label::primary("bitwise negation only works on integers", self.not_span),
Label::context(
format!("this expression resolves to {kind}"),
self.expr_span,
),
],
}
}

fn notes(&self) -> Vec<Note> {
use ErrorVariant::NonInteger;

match &self.variant {
NonInteger(..) => {
vec![
Note::CoerceValue,
Note::SeeDocs(
"type coercion".to_owned(),
Urls::func_docs("#coerce-functions"),
),
]
}
}
}
}

// -----------------------------------------------------------------------------

#[cfg(test)]
mod tests {

use crate::compiler::{TypeDef, expression::Literal};
use crate::test_type_def;

use super::*;

test_type_def![bitwise_not_integer {
expr: |_| BitwiseNot {
inner: Box::new(Literal::from(10).into())
},
want: TypeDef::integer().infallible(),
}];
}
31 changes: 28 additions & 3 deletions src/compiler/expression/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ impl Op {
impl Expression for Op {
fn resolve(&self, ctx: &mut Context) -> Resolved {
use crate::value::Value::{Boolean, Null};
use ast::Opcode::{Add, And, Div, Eq, Err, Ge, Gt, Le, Lt, Merge, Mul, Ne, Or, Sub};
use ast::Opcode::{
Add, And, BitwiseAnd, BitwiseOr, Div, Eq, Err, Ge, Gt, Le, Lt, Merge, Mul, Ne, Or, Sub,
};

match self.opcode {
Err => return self.lhs.resolve(ctx).or_else(|_| self.rhs.resolve(ctx)),
Expand Down Expand Up @@ -132,6 +134,8 @@ impl Expression for Op {
Lt => lhs.try_lt(rhs),
Le => lhs.try_le(rhs),
Merge => lhs.try_merge(rhs),
BitwiseAnd => lhs.try_bitwise_and(rhs),
BitwiseOr => lhs.try_bitwise_or(rhs),
And | Or | Err => unreachable!(),
}
.map_err(Into::into)
Expand All @@ -140,7 +144,9 @@ impl Expression for Op {
#[allow(clippy::too_many_lines)]
fn type_info(&self, state: &TypeState) -> TypeInfo {
use crate::value::Kind as K;
use ast::Opcode::{Add, And, Div, Eq, Err, Ge, Gt, Le, Lt, Merge, Mul, Ne, Or, Sub};
use ast::Opcode::{
Add, And, BitwiseAnd, BitwiseOr, Div, Eq, Err, Ge, Gt, Le, Lt, Merge, Mul, Ne, Or, Sub,
};
let original_state = state.clone();

let mut state = state.clone();
Expand Down Expand Up @@ -317,6 +323,15 @@ impl Expression for Op {
_ => unreachable!("Add, Sub, or Mul operation not handled"),
}
}

BitwiseAnd | BitwiseOr => {
let rhs_def = self.rhs.apply_type_info(&mut state);
if lhs_def.is_integer() && rhs_def.is_integer() {
lhs_def.union(rhs_def).infallible().with_kind(K::integer())
} else {
lhs_def.union(rhs_def).fallible().with_kind(K::integer())
}
}
};
TypeInfo::new(state, result)
}
Expand Down Expand Up @@ -439,7 +454,7 @@ mod tests {

use ast::{
Ident,
Opcode::{Add, And, Div, Eq, Err, Ge, Gt, Le, Lt, Mul, Ne, Or, Sub},
Opcode::{Add, And, BitwiseAnd, BitwiseOr, Div, Eq, Err, Ge, Gt, Le, Lt, Mul, Ne, Or, Sub},
};

use crate::compiler::expression::{Block, IfStatement, Literal, Predicate, Variable};
Expand Down Expand Up @@ -866,5 +881,15 @@ mod tests {
},
want: TypeDef::bytes().or_integer(),
}

bitwise_and_integer {
expr: |_| op(BitwiseAnd, 10, 15),
want: TypeDef::integer().infallible(),
}

bitwise_or_integer {
expr: |_| op(BitwiseOr, 10, 15),
want: TypeDef::integer().infallible(),
}
];
}
18 changes: 14 additions & 4 deletions src/compiler/expression/unary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::fmt;

use crate::compiler::{
Context, Expression,
expression::{Not, Resolved},
expression::{BitwiseNot, Not, Resolved},
state::{TypeInfo, TypeState},
};

Expand All @@ -21,35 +21,39 @@ impl Unary {
#[derive(Debug, Clone, PartialEq)]
pub enum Variant {
Not(Not),
BitwiseNot(BitwiseNot),
}

impl Expression for Unary {
fn resolve(&self, ctx: &mut Context) -> Resolved {
use Variant::Not;
use Variant::{BitwiseNot, Not};

match &self.variant {
Not(v) => v.resolve(ctx),
BitwiseNot(v) => v.resolve(ctx),
}
}

fn type_info(&self, state: &TypeState) -> TypeInfo {
use Variant::Not;
use Variant::{BitwiseNot, Not};

let mut state = state.clone();

let result = match &self.variant {
Not(v) => v.apply_type_info(&mut state),
BitwiseNot(v) => v.apply_type_info(&mut state),
};
TypeInfo::new(state, result)
}
}

impl fmt::Display for Unary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Variant::Not;
use Variant::{BitwiseNot, Not};

match &self.variant {
Not(v) => v.fmt(f),
BitwiseNot(v) => v.fmt(f),
}
}
}
Expand All @@ -59,3 +63,9 @@ impl From<Not> for Variant {
Variant::Not(not)
}
}

impl From<BitwiseNot> for Variant {
fn from(bitwise_not: BitwiseNot) -> Self {
Variant::BitwiseNot(bitwise_not)
}
}
3 changes: 3 additions & 0 deletions src/compiler/unused_expression_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ impl AstVisitor<'_> {
Unary::Not(not) => {
self.visit_node(&not.1, state);
}
Unary::BitwiseNot(not) => {
self.visit_node(&not.1, state);
}
},
Expr::Assignment(assignment) => {
self.visit_assignment(assignment, state);
Expand Down
Loading