difftreelog
feat detect infinite recursion in assertions
in: master
1 file changed
crates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth152#[derive(Trace, Debug)]152#[derive(Trace, Debug)]153enum CacheValue {153enum CacheValue {154 Cached(Result<Option<Val>>),154 Cached(Result<Option<Val>>),155 Pending,155 Pending { in_current_assertion: Cell<bool> },156}156}157157158pub type EnumFieldsHandler<'a> =158pub type EnumFieldsHandler<'a> =252thread_local! {252thread_local! {253 static RUNNING_ASSERTIONS: RefCell<FxHashSet<ObjValue>> = RefCell::default();253 static RUNNING_ASSERTIONS: RefCell<FxHashSet<ObjValue>> = RefCell::default();254}254}255fn is_asserting(obj: &ObjValue) -> bool {255fn is_asserting() -> bool {256 RUNNING_ASSERTIONS.with_borrow(|v| v.contains(obj))256 RUNNING_ASSERTIONS.with_borrow(|v| !v.is_empty())257}257}258/// Returns false if already asserting258struct AssertionGuard(ObjValue);259impl Drop for AssertionGuard {259fn start_asserting(obj: &ObjValue) -> bool {260 fn drop(&mut self) {260 RUNNING_ASSERTIONS.with_borrow_mut(|v| v.insert(obj.clone()))261 RUNNING_ASSERTIONS.with_borrow_mut(|v| {262 let r = v.remove(&self.0);263 debug_assert!(264 r,265 "finish_asserting was called before start_asserting or twice"266 );267 });261}268 }269}270/// Returns `None` if already asserting262fn finish_asserting(obj: &ObjValue) {271fn asserting(obj: &ObjValue) -> Option<AssertionGuard> {263 RUNNING_ASSERTIONS.with_borrow_mut(|v| {272 RUNNING_ASSERTIONS264 let r = v.remove(obj);273 .with_borrow_mut(|v| v.insert(obj.clone()).then(|| AssertionGuard(obj.clone())))265 debug_assert!(266 r,267 "finish_asserting was called before start_asserting or twice"268 );269 });270}274}271275272thread_local! {276thread_local! {625 match cache.entry(cache_key.clone()) {629 match cache.entry(cache_key.clone()) {626 Entry::Occupied(v) => match v.get() {630 Entry::Occupied(v) => match v.get() {627 CacheValue::Cached(v) => return v.clone(),631 CacheValue::Cached(v) => return v.clone(),628 CacheValue::Pending => {632 CacheValue::Pending {633 in_current_assertion,634 } => {635 // During assertions we may encounter field that triggered the first assertion. This field is pending,636 // but this time evaluation of this field would not trigger assertions in currently asserting object637 // again; thus the evaluation result will be different, as it won't trigger the assertion failure side638 // effect. The field vould be evaluated twice, yet the results should not be affected.629 if !is_asserting(self) {639 if !is_asserting() || in_current_assertion.get() {630 bail!(InfiniteRecursionDetected);640 bail!(InfiniteRecursionDetected);631 }641 }642 in_current_assertion.set(true);632 }643 }633 },644 },634 Entry::Vacant(v) => {645 Entry::Vacant(v) => {635 v.insert(CacheValue::Pending);646 v.insert(CacheValue::Pending {647 // Should not default to is_asserting(), as evaluation of this field may trigger the other object648 // assertions which would hit the same field again.649 in_current_assertion: Cell::default(),650 });636 }651 }637 }652 }638 }653 };639 let result = self.get_idx_uncached(key, core);654 let result = self.get_idx_uncached(key, core);640 {655 {641 let mut cache = self.0.value_cache.borrow_mut();656 let mut cache = self.0.value_cache.borrow_mut();746 if self.0.assertions_ran.get() {761 if self.0.assertions_ran.get() {747 return Ok(());762 return Ok(());748 }763 }749 if !start_asserting(self) {764 let Some(_asserting) = asserting(self) else {750 return Ok(());765 return Ok(());751 }766 };752 for (idx, ele) in self.0.cores.iter().enumerate() {767 for (idx, ele) in self.0.cores.iter().enumerate() {753 let sup_this = SupThis {768 let sup_this = SupThis {754 sup: CoreIdx { idx },769 sup: CoreIdx { idx },755 this: self.clone(),770 this: self.clone(),756 };771 };757 ele.0.run_assertions_core(sup_this).inspect_err(|_e| {772 ele.0.run_assertions_core(sup_this)?;758 finish_asserting(self);759 })?;760 }773 }761 finish_asserting(self);762 self.0.assertions_ran.set(true);774 self.0.assertions_ran.set(true);763 Ok(())775 Ok(())764 }776 }