Skip to content

Commit

Permalink
no_std support
Browse files Browse the repository at this point in the history
- Disable default features in dependencies
- Use anyhow::Error::msg to map errors. Note: anyhow will itself be removed later.
- lazy_static/spin_no_std used in no_std environments
- ensure_no_std binary is built to target  thumbv7m-none-eabi to ensure that
  there are no std dependencies.  thumbv7m-none-eabi target has no std support.
- The opa-no-std feature enables only those Regorus features that work with no_std.

Signed-off-by: Anand Krishnamoorthi <[email protected]>
  • Loading branch information
anakrish committed May 10, 2024
1 parent 01fc234 commit 883d561
Show file tree
Hide file tree
Showing 18 changed files with 132 additions and 62 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
- uses: actions/checkout@v3
- name: Add musl target
run: rustup target add x86_64-unknown-linux-musl
- name: Add no_std target
run: rustup target add thumbv7m-none-eabi
- name: Install musl-gcc
run: sudo apt update && sudo apt install -y musl-tools
- name: Format Check
Expand All @@ -26,6 +28,8 @@ jobs:
run: cargo build -r --all-features --verbose
- name: Build
run: cargo build -r --verbose
- name: Build no_std
run: cd tests/ensure_no_std && cargo build -r --target thumbv7m-none-eabi
- name: Doc Tests
run: cargo test -r --doc
- name: Run tests
Expand Down
61 changes: 41 additions & 20 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"bindings/wasm",
"bindings/java",
"bindings/ruby/ext/regorusrb",
"tests/ensure_no_std",
]

[package]
Expand Down Expand Up @@ -33,14 +34,15 @@ crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha1",
deprecated = []
hex = ["dep:data-encoding"]
http = []
jwt = ["dep:jsonwebtoken", "dep:data-encoding"]
glob = ["dep:wax"]
graph = []
jsonschema = ["dep:jsonschema"]
jwt = ["dep:jsonwebtoken", "dep:data-encoding", "dep:itertools"]
no_std = ["lazy_static/spin_no_std"]
opa-runtime = []
regex = ["dep:regex"]
semver = ["dep:semver"]
std = ["serde_json/std"]
std = ["rand", "serde_json/std"]
time = ["dep:chrono", "dep:chrono-tz"]
uuid = ["dep:uuid"]
urlquery = ["dep:url"]
Expand All @@ -67,39 +69,58 @@ full-opa = [
"yaml"
]

# Features that can be used in no_std environments.
# Note that: the spin_no_std feature in lazy_static must be specified.
opa-no-std = [
"arc",
"base64",
"base64url",
"coverage",
"crypto",
"deprecated",
"graph",
"hex",
"no_std",
"opa-runtime",
"regex",
"semver",
# Configure lazy_static to use spinlocks.
"lazy_static/spin_no_std"
]

# This feature enables some testing utils for OPA tests.
opa-testutil = []

[dependencies]
anyhow = { version = "1.0.45", default-features=false }
anyhow = { version = "1.0.45", default-features = false }
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc"] }
serde_json = { version = "1.0.89", default-features=false, features = ["alloc"] }
serde_yaml = {version = "0.9.16", optional = true }
lazy_static = "1.4.0"
rand = "0.8.5"
num = "0.4.1"
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
lazy_static = { version = "1.4.0", default-features = false }

# Crypto
constant_time_eq = {version = "0.3.0", optional = true}
hmac = {version = "0.12.1", optional = true}
sha2 = {version= "0.10.8", optional = true}
hex = {version = "0.4.3", optional = true}
sha1 = {version = "0.10.6", optional = true}
md-5 = {version = "0.10.6", optional = true}

data-encoding = { version = "2.4.0", optional = true }
constant_time_eq = {version = "0.3.0", optional = true, default-features = false }
hmac = {version = "0.12.1", optional = true, default-features = false}
sha2 = {version= "0.10.8", optional = true, default-features = false }
hex = {version = "0.4.3", optional = true, default-features = false, features = ["alloc"] }
sha1 = {version = "0.10.6", optional = true, default-features = false }
md-5 = {version = "0.10.6", optional = true, default-features = false }

data-encoding = { version = "2.4.0", optional = true, default-features=false, features = ["alloc"] }
scientific = { version = "0.5.2" }

regex = {version = "1.10.2", optional = true}
semver = {version = "1.0.20", optional = true}
regex = {version = "1.10.2", optional = true, default-features = false }
semver = {version = "1.0.20", optional = true, default-features = false }
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
url = { version = "2.5.0", optional = true }
uuid = { version = "1.6.1", features = ["v4", "fast-rng"], optional = true }
uuid = { version = "1.6.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.17.1", default-features = false, optional = true }
chrono = { version = "0.4.31", optional = true }
chrono-tz = { version = "0.8.5", optional = true }
jsonwebtoken = { version = "9.2.0", optional = true }
itertools = "0.12.1"
itertools = { version = "0.12.1", default-features = false, optional = true }

serde_yaml = {version = "0.9.16", default-features = false, optional = true }
rand = { version = "0.8.5", default-features = false, optional = true }

[dev-dependencies]
cfg-if = "1.0.0"
Expand Down
15 changes: 11 additions & 4 deletions scripts/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@
set -eo pipefail

if [ -f Cargo.toml ]; then
# Run precommit checks
# Run precommit checks.
dir=$(dirname "${BASH_SOURCE[0]}")
"$dir/pre-commit"

# Ensure that the public API works
# Ensure that the public API works.
cargo test -r --doc

# Ensure that we can build with all features
# Ensure that no_std build succeeds.
# Build for a target that has no std available.
if command -v rustup > /dev/null; then
rustup target add thumbv7m-none-eabi
(cd tests/ensure_no_std; cargo build -r --target thumbv7m-none-eabi)
fi

# Ensure that we can build with all features.
cargo build -r --all-features

# Ensure that all tests pass
# Ensure that all tests pass.
cargo test -r
cargo test -r --test aci
cargo test -r --test kata
Expand Down
2 changes: 1 addition & 1 deletion src/builtins/debugging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn print_to_string(
// Additionally interpreter must allow undefined inputs to print.
fn print(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let msg = print_to_string(span, params, args, strict)?;

let _ = core::convert::identity(&msg);
#[cfg(feature = "std")]
if !msg.is_empty() {
std::eprintln!("{}", &msg[1..]);
Expand Down
34 changes: 21 additions & 13 deletions src/builtins/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ fn base64_decode(
ensure_args_count(span, name, params, args, 1)?;

let encoded_str = ensure_string(name, &params[0], &args[0])?;
let decoded_bytes = data_encoding::BASE64.decode(encoded_str.as_bytes())?;
let decoded_bytes = data_encoding::BASE64
.decode(encoded_str.as_bytes())
.map_err(|e| {
params[0]
.span()
.error(&format!("decode failed\nCaused by\n{e}"))
})?;
Ok(Value::String(
String::from_utf8_lossy(&decoded_bytes).into(),
))
Expand Down Expand Up @@ -173,7 +179,13 @@ fn hex_decode(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)
ensure_args_count(span, name, params, args, 1)?;

let encoded_str = ensure_string(name, &params[0], &args[0])?;
let decoded_bytes = data_encoding::HEXLOWER_PERMISSIVE.decode(encoded_str.as_bytes())?;
let decoded_bytes = data_encoding::HEXLOWER_PERMISSIVE
.decode(encoded_str.as_bytes())
.map_err(|e| {
params[0]
.span()
.error(&format!("decode failure\nCaused by\n{e}"))
})?;
Ok(Value::String(
String::from_utf8_lossy(&decoded_bytes).into(),
))
Expand Down Expand Up @@ -361,11 +373,9 @@ fn json_is_valid(
fn json_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "json.marshal";
ensure_args_count(span, name, params, args, 1)?;
Ok(Value::String(
serde_json::to_string(&args[0])
.with_context(|| span.error("could not serialize to json"))?
.into(),
))
Ok(Value::from(serde_json::to_string(&args[0]).map_err(
|e| span.error(&format!("could not serialize to json\nCaused by\n{e}")),
)?))
}

fn json_marshal_with_options(
Expand Down Expand Up @@ -406,15 +416,13 @@ fn json_marshal_with_options(
}

if !pretty || options.is_empty() {
return Ok(Value::String(
serde_json::to_string(&args[0])
.with_context(|| span.error("could not serialize to json"))?
.into(),
));
return Ok(Value::from(serde_json::to_string(&args[0]).map_err(
|e| span.error(&format!("could not serialize to json\nCaused by\n{e}")),
)?));
}

let lines: Vec<String> = serde_json::to_string_pretty(&args[0])
.with_context(|| span.error("could not serialize to json"))?
.map_err(|e| span.error(&format!("could not serialize to json\nCaused by\n{e}")))?
.split('\n')
.map(|line| {
let mut line = line.to_string();
Expand Down
8 changes: 6 additions & 2 deletions src/builtins/numbers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@

use crate::ast::{ArithOp, Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string};
use crate::builtins::utils::{ensure_args_count, ensure_numeric};
use crate::lexer::Span;
use crate::number::Number;
use crate::value::Value;
use crate::*;

use anyhow::{bail, Result};

#[cfg(feature = "std")]
use rand::{thread_rng, Rng};

pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
Expand All @@ -18,6 +20,7 @@ pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn
m.insert("floor", (floor, 1));
m.insert("numbers.range", (range, 2));
m.insert("numbers.range_step", (range_step, 3));
#[cfg(feature = "std")]
m.insert("rand.intn", (intn, 2));
m.insert("round", (round, 1));
}
Expand Down Expand Up @@ -155,10 +158,11 @@ fn round(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
))
}

#[cfg(feature = "std")]
fn intn(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let fcn = "rand.intn";
ensure_args_count(span, fcn, params, args, 2)?;
let _ = ensure_string(fcn, &params[0], &args[0])?;
let _ = crate::builtins::utils::ensure_string(fcn, &params[0], &args[0])?;
let n = ensure_numeric(fcn, &params[0], &args[1])?;

Ok(match n.as_u64() {
Expand Down
1 change: 1 addition & 0 deletions src/builtins/opa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ fn opa_runtime(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)
);

// Emitting environment variables could lead to confidential data being leaked.
#[cfg(feature = "std")]
if false {
obj.insert(
Value::String("env".into()),
Expand Down
4 changes: 2 additions & 2 deletions src/builtins/semver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ fn compare(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->

let v1 = ensure_string(name, &params[0], &args[0])?;
let v2 = ensure_string(name, &params[1], &args[1])?;
let version1 = Version::parse(&v1)?;
let version2 = Version::parse(&v2)?;
let version1 = Version::parse(&v1).map_err(|_| params[0].span().error("invalid semver"))?;
let version2 = Version::parse(&v2).map_err(|_| params[0].span().error("invalid semver"))?;
let result = match version1.cmp_precedence(&version2) {
Ordering::Less => -1,
Ordering::Equal => 0,
Expand Down
4 changes: 3 additions & 1 deletion src/builtins/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::builtins::time;
use crate::builtins::utils::{ensure_args_count, ensure_string};
use crate::lexer::Span;
use crate::value::Value;
use crate::*;

use std::thread;

Expand All @@ -21,7 +22,8 @@ fn sleep(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
ensure_args_count(span, name, params, args, 1)?;

let val = ensure_string(name, &params[0], &args[0])?;
let dur = time::compat::parse_duration(val.as_ref())?;
let dur = time::compat::parse_duration(val.as_ref())
.map_err(|e| params[0].span().error(&format!("{e}")))?;

thread::sleep(dur.to_std()?);

Expand Down
2 changes: 1 addition & 1 deletion src/builtins/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ fn parse_duration_ns(
ensure_args_count(span, name, params, args, 1)?;

let value = ensure_string(name, &params[0], &args[0])?;
let dur = compat::parse_duration(value.as_ref())?;
let dur = compat::parse_duration(value.as_ref()).map_err(anyhow::Error::msg)?;
safe_timestamp_nanos(span, strict, dur.num_nanoseconds())
}

Expand Down
3 changes: 0 additions & 3 deletions src/builtins/time/compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
use crate::*;
use core::fmt;
use core::iter;
use std::error::Error;

use chrono::TimeZone;
use chrono::{
Expand Down Expand Up @@ -72,8 +71,6 @@ impl fmt::Display for ParseDurationError {
}
}

impl Error for ParseDurationError {}

// Parses a duration string in the form of `10h12m45s`.
//
// Adapted from Go's `time.ParseDuration`:
Expand Down
6 changes: 3 additions & 3 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1743,9 +1743,9 @@ impl Interpreter {
span.col,
format!(
"value for key `{}` generated multiple times: `{}` and `{}`",
serde_json::to_string_pretty(&key)?,
serde_json::to_string_pretty(&pv)?,
serde_json::to_string_pretty(&value)?,
serde_json::to_string_pretty(&key).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&pv).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&value).map_err(anyhow::Error::msg)?,
)
.as_str(),
));
Expand Down
19 changes: 9 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,30 +404,29 @@ pub mod coverage {
/// <img src="https://github.com/microsoft/regorus/blob/main/docs/coverage.png?raw=true">
pub fn to_colored_string(&self) -> anyhow::Result<String> {
use std::io::Write;
let mut s = Vec::new();
writeln!(&mut s, "COVERAGE REPORT:")?;
let mut s = String::default();
s.push_str("COVERAGE REPORT:\n");
for file in self.files.iter() {
if file.not_covered.is_empty() {
writeln!(&mut s, "{} has full coverage", file.path)?;
s.push_str(&format!("{} has full coverage\n", file.path));
continue;
}

writeln!(&mut s, "{}:", file.path)?;
s.push_str(&format!("{}:", file.path));
for (line, code) in file.code.split('\n').enumerate() {
let line = line as u32 + 1;
if file.not_covered.contains(&line) {
writeln!(&mut s, "\x1b[31m {line:4} {code}\x1b[0m")?;
s.push_str(&format!("\x1b[31m {line:4} {code}\x1b[0m\n"));
} else if file.covered.contains(&line) {
writeln!(&mut s, "\x1b[32m {line:4} {code}\x1b[0m")?;
s.push_str(&format!("\x1b[32m {line:4} {code}\x1b[0m\n"));
} else {
writeln!(&mut s, " {line:4} {code}")?;
s.push_str(&format!(" {line:4} {code}\n"));
}
}
}

writeln!(&mut s)?;
Ok(core::str::from_utf8(&s)?.to_string())
s.push('\n');
Ok(s)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl<'source> Parser<'source> {
let msg = format!(
"`{kw}` will be treated as identifier due to missing `import future.keywords.{kw}`"
);
let _ = core::convert::identity(&msg);
#[cfg(feature = "std")]
std::println!(
"{}",
Expand Down
Loading

0 comments on commit 883d561

Please sign in to comment.