difftreelog
fix string index bounds check
in: master
2 files changed
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -36,6 +36,8 @@
#[error("array out of bounds: {0} is not within [0,{1})")]
ArrayBoundsError(usize, usize),
+ #[error("string out of bounds: {0} is not within [0,{1})")]
+ StringBoundsError(usize, usize),
#[error("assert failed: {0}")]
AssertionFailed(IStr),
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use gcmodule::{Cc, Trace};2use jrsonnet_interner::IStr;3use jrsonnet_parser::{4 ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, ForSpecData, IfSpecData,5 LiteralType, LocExpr, Member, ObjBody, ParamsDesc,6};7use jrsonnet_types::ValType;89use crate::{10 builtin::{std_slice, BUILTINS},11 error::Error::*,12 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},13 function::CallLocation,14 gc::TraceBox,15 throw,16 typed::Typed,17 val::{ArrValue, FuncDesc, FuncVal, LazyValValue},18 Bindable, Context, ContextCreator, FutureWrapper, GcHashMap, LazyBinding, LazyVal, ObjValue,19 ObjValueBuilder, ObjectAssertion, Result, State, Val,20};21pub mod operator;2223pub fn evaluate_binding_in_future(b: &BindSpec, fctx: FutureWrapper<Context>) -> LazyVal {24 let b = b.clone();25 if let Some(params) = &b.params {26 let params = params.clone();2728 #[derive(Trace)]29 struct LazyMethodBinding {30 fctx: FutureWrapper<Context>,31 name: IStr,32 params: ParamsDesc,33 value: LocExpr,34 }35 impl LazyValValue for LazyMethodBinding {36 fn get(self: Box<Self>, _: State) -> Result<Val> {37 Ok(evaluate_method(38 self.fctx.unwrap(),39 self.name,40 self.params,41 self.value,42 ))43 }44 }4546 LazyVal::new(TraceBox(Box::new(LazyMethodBinding {47 fctx,48 name: b.name.clone(),49 params,50 value: b.value.clone(),51 })))52 } else {53 #[derive(Trace)]54 struct LazyNamedBinding {55 fctx: FutureWrapper<Context>,56 name: IStr,57 value: LocExpr,58 }59 impl LazyValValue for LazyNamedBinding {60 fn get(self: Box<Self>, s: State) -> Result<Val> {61 evaluate_named(s, self.fctx.unwrap(), &self.value, self.name)62 }63 }64 LazyVal::new(TraceBox(Box::new(LazyNamedBinding {65 fctx,66 name: b.name.clone(),67 value: b.value,68 })))69 }70}7172pub fn evaluate_binding(b: &BindSpec, cctx: ContextCreator) -> (IStr, LazyBinding) {73 let b = b.clone();74 if let Some(params) = &b.params {75 let params = params.clone();7677 #[derive(Trace)]78 struct BindableMethodLazyVal {79 this: Option<ObjValue>,80 super_obj: Option<ObjValue>,8182 cctx: ContextCreator,83 name: IStr,84 params: ParamsDesc,85 value: LocExpr,86 }87 impl LazyValValue for BindableMethodLazyVal {88 fn get(self: Box<Self>, s: State) -> Result<Val> {89 Ok(evaluate_method(90 self.cctx.create(s, self.this, self.super_obj)?,91 self.name,92 self.params,93 self.value,94 ))95 }96 }9798 #[derive(Trace)]99 struct BindableMethod {100 cctx: ContextCreator,101 name: IStr,102 params: ParamsDesc,103 value: LocExpr,104 }105 impl Bindable for BindableMethod {106 fn bind(107 &self,108 _: State,109 this: Option<ObjValue>,110 super_obj: Option<ObjValue>,111 ) -> Result<LazyVal> {112 Ok(LazyVal::new(TraceBox(Box::new(BindableMethodLazyVal {113 this,114 super_obj,115116 cctx: self.cctx.clone(),117 name: self.name.clone(),118 params: self.params.clone(),119 value: self.value.clone(),120 }))))121 }122 }123124 (125 b.name.clone(),126 LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableMethod {127 cctx,128 name: b.name.clone(),129 params,130 value: b.value.clone(),131 })))),132 )133 } else {134 #[derive(Trace)]135 struct BindableNamedLazyVal {136 this: Option<ObjValue>,137 super_obj: Option<ObjValue>,138139 cctx: ContextCreator,140 name: IStr,141 value: LocExpr,142 }143 impl LazyValValue for BindableNamedLazyVal {144 fn get(self: Box<Self>, s: State) -> Result<Val> {145 evaluate_named(146 s.clone(),147 self.cctx.create(s, self.this, self.super_obj)?,148 &self.value,149 self.name,150 )151 }152 }153154 #[derive(Trace)]155 struct BindableNamed {156 cctx: ContextCreator,157 name: IStr,158 value: LocExpr,159 }160 impl Bindable for BindableNamed {161 fn bind(162 &self,163 _: State,164 this: Option<ObjValue>,165 super_obj: Option<ObjValue>,166 ) -> Result<LazyVal> {167 Ok(LazyVal::new(TraceBox(Box::new(BindableNamedLazyVal {168 this,169 super_obj,170171 cctx: self.cctx.clone(),172 name: self.name.clone(),173 value: self.value.clone(),174 }))))175 }176 }177178 (179 b.name.clone(),180 LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableNamed {181 cctx,182 name: b.name.clone(),183 value: b.value.clone(),184 })))),185 )186 }187}188189pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {190 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {191 name,192 ctx,193 params,194 body,195 })))196}197198pub fn evaluate_field_name(199 s: State,200 ctx: Context,201 field_name: &jrsonnet_parser::FieldName,202) -> Result<Option<IStr>> {203 Ok(match field_name {204 jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),205 jrsonnet_parser::FieldName::Dyn(expr) => s.push(206 CallLocation::new(&expr.1),207 || "evaluating field name".to_string(),208 || {209 let value = evaluate(s.clone(), ctx, expr)?;210 if matches!(value, Val::Null) {211 Ok(None)212 } else {213 Ok(Some(IStr::from_untyped(value, s.clone())?))214 }215 },216 )?,217 })218}219220pub fn evaluate_comp(221 s: State,222 ctx: Context,223 specs: &[CompSpec],224 callback: &mut impl FnMut(Context) -> Result<()>,225) -> Result<()> {226 match specs.get(0) {227 None => callback(ctx)?,228 Some(CompSpec::IfSpec(IfSpecData(cond))) => {229 if bool::from_untyped(evaluate(s.clone(), ctx.clone(), cond)?, s.clone())? {230 evaluate_comp(s, ctx, &specs[1..], callback)?231 }232 }233 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {234 match evaluate(s.clone(), ctx.clone(), expr)? {235 Val::Arr(list) => {236 for item in list.iter(s.clone()) {237 evaluate_comp(238 s.clone(),239 ctx.clone().with_var(var.clone(), item?.clone()),240 &specs[1..],241 callback,242 )?243 }244 }245 _ => throw!(InComprehensionCanOnlyIterateOverArray),246 }247 }248 }249 Ok(())250}251252pub fn evaluate_member_list_object(s: State, ctx: Context, members: &[Member]) -> Result<ObjValue> {253 let new_bindings = FutureWrapper::new();254 let future_this = FutureWrapper::new();255 let cctx = ContextCreator(ctx.clone(), new_bindings.clone());256 {257 let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());258 for (n, b) in members259 .iter()260 .filter_map(|m| match m {261 Member::BindStmt(b) => Some(b.clone()),262 _ => None,263 })264 .map(|b| evaluate_binding(&b, cctx.clone()))265 {266 bindings.insert(n, b);267 }268 new_bindings.fill(bindings);269 }270271 let mut builder = ObjValueBuilder::new();272 for member in members.iter() {273 match member {274 Member::Field(FieldMember {275 name,276 plus,277 params: None,278 visibility,279 value,280 }) => {281 let name = evaluate_field_name(s.clone(), ctx.clone(), name)?;282 if name.is_none() {283 continue;284 }285 let name = name.unwrap();286287 #[derive(Trace)]288 struct ObjMemberBinding {289 cctx: ContextCreator,290 value: LocExpr,291 name: IStr,292 }293 impl Bindable for ObjMemberBinding {294 fn bind(295 &self,296 s: State,297 this: Option<ObjValue>,298 super_obj: Option<ObjValue>,299 ) -> Result<LazyVal> {300 Ok(LazyVal::new_resolved(evaluate_named(301 s.clone(),302 self.cctx.create(s, this, super_obj)?,303 &self.value,304 self.name.clone(),305 )?))306 }307 }308 builder309 .member(name.clone())310 .with_add(*plus)311 .with_visibility(*visibility)312 .with_location(value.1.clone())313 .bindable(314 s.clone(),315 TraceBox(Box::new(ObjMemberBinding {316 cctx: cctx.clone(),317 value: value.clone(),318 name,319 })),320 )?;321 }322 Member::Field(FieldMember {323 name,324 params: Some(params),325 value,326 ..327 }) => {328 let name = evaluate_field_name(s.clone(), ctx.clone(), name)?;329 if name.is_none() {330 continue;331 }332 let name = name.unwrap();333 #[derive(Trace)]334 struct ObjMemberBinding {335 cctx: ContextCreator,336 value: LocExpr,337 params: ParamsDesc,338 name: IStr,339 }340 impl Bindable for ObjMemberBinding {341 fn bind(342 &self,343 s: State,344 this: Option<ObjValue>,345 super_obj: Option<ObjValue>,346 ) -> Result<LazyVal> {347 Ok(LazyVal::new_resolved(evaluate_method(348 self.cctx.create(s, this, super_obj)?,349 self.name.clone(),350 self.params.clone(),351 self.value.clone(),352 )))353 }354 }355 builder356 .member(name.clone())357 .hide()358 .with_location(value.1.clone())359 .bindable(360 s.clone(),361 TraceBox(Box::new(ObjMemberBinding {362 cctx: cctx.clone(),363 value: value.clone(),364 params: params.clone(),365 name,366 })),367 )?;368 }369 Member::BindStmt(_) => {}370 Member::AssertStmt(stmt) => {371 #[derive(Trace)]372 struct ObjectAssert {373 cctx: ContextCreator,374 assert: AssertStmt,375 }376 impl ObjectAssertion for ObjectAssert {377 fn run(378 &self,379 s: State,380 this: Option<ObjValue>,381 super_obj: Option<ObjValue>,382 ) -> Result<()> {383 let ctx = self.cctx.create(s.clone(), this, super_obj)?;384 evaluate_assert(s, ctx, &self.assert)385 }386 }387 builder.assert(TraceBox(Box::new(ObjectAssert {388 cctx: cctx.clone(),389 assert: stmt.clone(),390 })));391 }392 }393 }394 let this = builder.build();395 future_this.fill(this.clone());396 Ok(this)397}398399pub fn evaluate_object(s: State, ctx: Context, object: &ObjBody) -> Result<ObjValue> {400 Ok(match object {401 ObjBody::MemberList(members) => evaluate_member_list_object(s, ctx, members)?,402 ObjBody::ObjComp(obj) => {403 let future_this = FutureWrapper::new();404 let mut builder = ObjValueBuilder::new();405 evaluate_comp(s.clone(), ctx, &obj.compspecs, &mut |ctx| {406 let new_bindings = FutureWrapper::new();407 let cctx = ContextCreator(ctx.clone(), new_bindings.clone());408 let mut bindings: GcHashMap<IStr, LazyBinding> =409 GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len());410 for (n, b) in obj411 .pre_locals412 .iter()413 .chain(obj.post_locals.iter())414 .map(|b| evaluate_binding(b, cctx.clone()))415 {416 bindings.insert(n, b);417 }418 new_bindings.fill(bindings.clone());419 let ctx = ctx.extend_unbound(s.clone(), bindings, None, None, None)?;420 let key = evaluate(s.clone(), ctx.clone(), &obj.key)?;421422 match key {423 Val::Null => {}424 Val::Str(n) => {425 #[derive(Trace)]426 struct ObjCompBinding {427 ctx: Context,428 value: LocExpr,429 }430 impl Bindable for ObjCompBinding {431 fn bind(432 &self,433 s: State,434 this: Option<ObjValue>,435 _super_obj: Option<ObjValue>,436 ) -> Result<LazyVal> {437 Ok(LazyVal::new_resolved(evaluate(438 s,439 self.ctx.clone().extend(GcHashMap::new(), None, this, None),440 &self.value,441 )?))442 }443 }444 builder445 .member(n)446 .with_location(obj.value.1.clone())447 .with_add(obj.plus)448 .bindable(449 s.clone(),450 TraceBox(Box::new(ObjCompBinding {451 ctx,452 value: obj.value.clone(),453 })),454 )?;455 }456 v => throw!(FieldMustBeStringGot(v.value_type())),457 }458459 Ok(())460 })?;461462 let this = builder.build();463 future_this.fill(this.clone());464 this465 }466 })467}468469pub fn evaluate_apply(470 s: State,471 ctx: Context,472 value: &LocExpr,473 args: &ArgsDesc,474 loc: CallLocation,475 tailstrict: bool,476) -> Result<Val> {477 let value = evaluate(s.clone(), ctx.clone(), value)?;478 Ok(match value {479 Val::Func(f) => {480 let body = || f.evaluate(s.clone(), ctx, loc, args, tailstrict);481 if tailstrict {482 body()?483 } else {484 s.push(loc, || format!("function <{}> call", f.name()), body)?485 }486 }487 v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),488 })489}490491pub fn evaluate_assert(s: State, ctx: Context, assertion: &AssertStmt) -> Result<()> {492 let value = &assertion.0;493 let msg = &assertion.1;494 let assertion_result = s.push(495 CallLocation::new(&value.1),496 || "assertion condition".to_owned(),497 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),498 )?;499 if !assertion_result {500 s.push(501 CallLocation::new(&value.1),502 || "assertion failure".to_owned(),503 || {504 if let Some(msg) = msg {505 throw!(AssertionFailed(506 evaluate(s.clone(), ctx, msg)?.to_string(s.clone())?507 ));508 } else {509 throw!(AssertionFailed(Val::Null.to_string(s.clone())?));510 }511 },512 )?513 }514 Ok(())515}516517pub fn evaluate_named(s: State, ctx: Context, lexpr: &LocExpr, name: IStr) -> Result<Val> {518 use Expr::*;519 let LocExpr(expr, _loc) = lexpr;520 Ok(match &**expr {521 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),522 _ => evaluate(s, ctx, lexpr)?,523 })524}525526pub fn evaluate(s: State, ctx: Context, expr: &LocExpr) -> Result<Val> {527 use Expr::*;528 let LocExpr(expr, loc) = expr;529 // let bp = with_state(|s| s.0.stop_at.borrow().clone());530 Ok(match &**expr {531 Literal(LiteralType::This) => {532 Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)533 }534 Literal(LiteralType::Super) => Val::Obj(535 ctx.super_obj()536 .clone()537 .ok_or(NoSuperFound)?538 .with_this(ctx.this().clone().unwrap()),539 ),540 Literal(LiteralType::Dollar) => {541 Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)542 }543 Literal(LiteralType::True) => Val::Bool(true),544 Literal(LiteralType::False) => Val::Bool(false),545 Literal(LiteralType::Null) => Val::Null,546 Parened(e) => evaluate(s, ctx, e)?,547 Str(v) => Val::Str(v.clone()),548 Num(v) => Val::new_checked_num(*v)?,549 BinaryOp(v1, o, v2) => evaluate_binary_op_special(s, ctx, v1, *o, v2)?,550 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,551 Var(name) => s.push(552 CallLocation::new(loc),553 || format!("variable <{}> access", name),554 || ctx.binding(name.clone())?.evaluate(s.clone()),555 )?,556 Index(value, index) => {557 match (558 evaluate(s.clone(), ctx.clone(), value)?,559 evaluate(s.clone(), ctx, index)?,560 ) {561 (Val::Obj(v), Val::Str(key)) => s.push(562 CallLocation::new(loc),563 || format!("field <{}> access", key),564 || {565 if let Some(v) = v.get(s.clone(), key.clone())? {566 Ok(v)567 } else {568 throw!(NoSuchField(key.clone()))569 }570 },571 )?,572 (Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(573 ValType::Obj,574 ValType::Str,575 n.value_type(),576 )),577578 (Val::Arr(v), Val::Num(n)) => {579 if n.fract() > f64::EPSILON {580 throw!(FractionalIndex)581 }582 v.get(s, n as usize)?583 .ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?584 }585 (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),586 (Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(587 ValType::Arr,588 ValType::Num,589 n.value_type(),590 )),591592 (Val::Str(s), Val::Num(n)) => Val::Str(593 s.chars()594 .skip(n as usize)595 .take(1)596 .collect::<String>()597 .into(),598 ),599 (Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(600 ValType::Str,601 ValType::Num,602 n.value_type(),603 )),604605 (v, _) => throw!(CantIndexInto(v.value_type())),606 }607 }608 LocalExpr(bindings, returned) => {609 let mut new_bindings: GcHashMap<IStr, LazyVal> =610 GcHashMap::with_capacity(bindings.len());611 let fctx = Context::new_future();612 for b in bindings {613 new_bindings.insert(b.name.clone(), evaluate_binding_in_future(b, fctx.clone()));614 }615 let ctx = ctx.extend_bound(new_bindings).into_future(fctx);616 evaluate(s, ctx, &returned.clone())?617 }618 Arr(items) => {619 let mut out = Vec::with_capacity(items.len());620 for item in items {621 // TODO: Implement ArrValue::Lazy with same context for every element?622 #[derive(Trace)]623 struct ArrayElement {624 ctx: Context,625 item: LocExpr,626 }627 impl LazyValValue for ArrayElement {628 fn get(self: Box<Self>, s: State) -> Result<Val> {629 evaluate(s, self.ctx, &self.item)630 }631 }632 out.push(LazyVal::new(TraceBox(Box::new(ArrayElement {633 ctx: ctx.clone(),634 item: item.clone(),635 }))));636 }637 Val::Arr(out.into())638 }639 ArrComp(expr, comp_specs) => {640 let mut out = Vec::new();641 evaluate_comp(s.clone(), ctx, comp_specs, &mut |ctx| {642 out.push(evaluate(s.clone(), ctx, expr)?);643 Ok(())644 })?;645 Val::Arr(ArrValue::Eager(Cc::new(out)))646 }647 Obj(body) => Val::Obj(evaluate_object(s, ctx, body)?),648 ObjExtend(a, b) => evaluate_add_op(649 s.clone(),650 &evaluate(s.clone(), ctx.clone(), a)?,651 &Val::Obj(evaluate_object(s, ctx, b)?),652 )?,653 Apply(value, args, tailstrict) => {654 evaluate_apply(s, ctx, value, args, CallLocation::new(loc), *tailstrict)?655 }656 Function(params, body) => {657 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())658 }659 Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(660 BUILTINS661 .with(|b| b.get(name).copied())662 .ok_or_else(|| IntrinsicNotFound(name.clone()))?,663 )),664 AssertExpr(assert, returned) => {665 evaluate_assert(s.clone(), ctx.clone(), assert)?;666 evaluate(s, ctx, returned)?667 }668 ErrorStmt(e) => s.push(669 CallLocation::new(loc),670 || "error statement".to_owned(),671 || {672 throw!(RuntimeError(673 evaluate(s.clone(), ctx, e)?.to_string(s.clone())?,674 ))675 },676 )?,677 IfElse {678 cond,679 cond_then,680 cond_else,681 } => {682 if s.push(683 CallLocation::new(loc),684 || "if condition".to_owned(),685 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),686 )? {687 evaluate(s, ctx, cond_then)?688 } else {689 match cond_else {690 Some(v) => evaluate(s, ctx, v)?,691 None => Val::Null,692 }693 }694 }695 Slice(value, desc) => {696 let indexable = evaluate(s.clone(), ctx.clone(), value)?;697 let loc = CallLocation::new(loc);698699 fn parse_idx<T: Typed>(700 loc: CallLocation,701 s: State,702 ctx: &Context,703 expr: &Option<LocExpr>,704 desc: &'static str,705 ) -> Result<Option<T>> {706 if let Some(value) = expr {707 Ok(Some(s.push(708 loc,709 || format!("slice {}", desc),710 || T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),711 )?))712 } else {713 Ok(None)714 }715 }716717 let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;718 let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;719 let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;720721 std_slice(indexable.into_indexable()?, start, end, step)?722 }723 Import(path) => {724 let tmp = loc.clone().0;725 let mut import_location = tmp.to_path_buf();726 import_location.pop();727 s.push(728 CallLocation::new(loc),729 || format!("import {:?}", path),730 || s.import_file(&import_location, path),731 )?732 }733 ImportStr(path) => {734 let tmp = loc.clone().0;735 let mut import_location = tmp.to_path_buf();736 import_location.pop();737 Val::Str(s.import_file_str(&import_location, path)?)738 }739 ImportBin(path) => {740 let tmp = loc.clone().0;741 let mut import_location = tmp.to_path_buf();742 import_location.pop();743 let bytes = s.import_file_bin(&import_location, path)?;744 Val::Arr(ArrValue::Bytes(bytes))745 }746 })747}