1234567891011121314151617use std::rc::Rc;1819use drop_bomb::DropBomb;20use jrsonnet_gcmodule::Acyclic;21use jrsonnet_interner::IStr;22use jrsonnet_ir::{23 ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BinaryOpType, BindSpec, CompSpec, Destruct, Expr,24 ExprParams, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, LiteralType, NumValue,25 ObjBody, ObjComp, ObjMembers, Slice, SliceDesc, Span, Spanned, UnaryOpType, Visibility,26 function::FunctionSignature,27};28use rustc_hash::FxHashMap;29use smallvec::SmallVec;3031use crate::{32 arr::arridx,33 error::{format_found, suggest_names},34};3536#[derive(Debug, Clone, Copy)]37#[must_use]38pub struct AnalysisResult {39 40 pub object_dependent_depth: u32,41 42 pub local_dependent_depth: u32,43}4445impl Default for AnalysisResult {46 fn default() -> Self {47 Self {48 object_dependent_depth: u32::MAX,49 local_dependent_depth: u32::MAX,50 }51 }52}5354impl AnalysisResult {55 fn depend_on_object(&mut self, depth: u32) {56 if depth < self.object_dependent_depth {57 self.object_dependent_depth = depth;58 }59 }60 fn depend_on_local(&mut self, depth: u32) {61 if depth < self.local_dependent_depth {62 self.local_dependent_depth = depth;63 }64 }65 fn taint_by(&mut self, other: AnalysisResult) {66 self.depend_on_object(other.object_dependent_depth);67 self.depend_on_local(other.local_dependent_depth);68 }69}7071#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Acyclic)]72pub struct LocalId(pub u32);7374impl LocalId {75 fn idx(self) -> usize {76 self.0 as usize77 }78 fn defined_before(self, other: Self) -> bool {79 self.0 < other.080 }81}8283#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Acyclic)]84pub enum LSlot {85 86 Local(LocalSlot),87 88 Capture(CaptureSlot),89}9091#[derive(Debug, Acyclic)]92pub struct ClosureShape {93 pub captures: Box<[LSlot]>,94 pub n_locals: u16,95}9697struct LocalDefinition {98 name: IStr,99 span: Option<Span>,100 101 defined_at_depth: u32,102 103 104 105 106 107 108 109 110 111 112 used_at_depth: u32,113 114 used_by_sibling: bool,115 116 analysis: AnalysisResult,117 118 119 analyzed: bool,120 121 122 123 scratch_referenced: bool,124}125126impl LocalDefinition {127 fn use_at(&mut self, depth: u32) {128 if depth == self.defined_at_depth {129 self.used_by_sibling = true;130 return;131 }132 if depth < self.used_at_depth {133 self.used_at_depth = depth;134 }135 }136}137138#[derive(Debug, Acyclic)]139pub enum LExpr {140 Slot(LSlot),141 Null,142 Bool(bool),143 Str(IStr),144 Num(NumValue),145 Arr {146 shape: ClosureShape,147 items: Rc<Vec<LExpr>>,148 },149 ArrComp(Box<LArrComp>),150 Obj(LObjBody),151 ObjExtend(Box<LExpr>, LObjBody),152 UnaryOp(UnaryOpType, Box<LExpr>),153 BinaryOp {154 lhs: Box<LExpr>,155 op: BinaryOpType,156 rhs: Box<LExpr>,157 },158 AssertExpr {159 assert: Rc<LAssertStmt>,160 rest: Box<LExpr>,161 },162 Error(Span, Box<LExpr>),163 LocalExpr(Box<LLocalExpr>),164 Import {165 kind: Spanned<ImportKind>,166 kind_span: Span,167 path: IStr,168 },169 Apply {170 applicable: Box<LExpr>,171 args: Spanned<LArgsDesc>,172 tailstrict: bool,173 },174 Index {175 indexable: Box<LExpr>,176 parts: Vec<LIndexPart>,177 },178 Function(Rc<LFunction>),179 IdentityFunction,180 IfElse {181 cond: Box<LExpr>,182 cond_then: Box<LExpr>,183 cond_else: Option<Box<LExpr>>,184 },185 Slice(Box<LSliceExpr>),186 Super,187188 189 190 BadLocal(&'static str),191}192193#[derive(Debug, Acyclic)]194pub struct LLocalExpr {195 pub frame_shape: ClosureShape,196 pub binds: Vec<LBind>,197 pub body: LExpr,198}199200#[derive(Debug, Acyclic)]201pub struct LFunction {202 pub name: Option<IStr>,203 pub params: Vec<LParam>,204 pub signature: FunctionSignature,205206 pub body_shape: ClosureShape,207 pub body: Rc<LExpr>,208}209210#[derive(Debug, Acyclic)]211pub struct LParam {212 pub name: Option<IStr>,213 pub destruct: LDestruct,214215 pub default: Option<(ClosureShape, Rc<LExpr>)>,216}217218#[derive(Debug, Acyclic)]219pub struct LBind {220 pub destruct: LDestruct,221 pub value_shape: ClosureShape,222 pub value: Rc<LExpr>,223}224225#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Acyclic)]226pub struct CaptureSlot(pub(crate) u16);227#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Acyclic)]228pub struct LocalSlot(pub(crate) u16);229230#[derive(Debug, Acyclic)]231pub enum LDestruct {232 Full(LocalSlot),233 #[cfg(feature = "exp-destruct")]234 Skip,235 #[cfg(feature = "exp-destruct")]236 Array {237 start: Vec<LDestruct>,238 rest: Option<LDestructRest>,239 end: Vec<LDestruct>,240 },241 #[cfg(feature = "exp-destruct")]242 Object {243 fields: Vec<LDestructField>,244 rest: Option<LDestructRest>,245 },246}247248#[derive(Debug, Clone, Copy, Acyclic)]249pub enum LDestructRest {250 Keep(LocalSlot),251 Drop,252}253254#[derive(Debug, Acyclic)]255pub struct LDestructField {256 pub name: IStr,257 pub into: Option<LDestruct>,258 pub default: Option<(ClosureShape, Rc<LExpr>)>,259}260261impl LDestruct {262 pub fn each_slot<F: FnMut(LocalSlot)>(&self, f: &mut F) {263 match self {264 Self::Full(s) => f(*s),265 #[cfg(feature = "exp-destruct")]266 Self::Skip => {}267 #[cfg(feature = "exp-destruct")]268 Self::Array { start, rest, end } => {269 for d in start {270 d.each_slot(f);271 }272 if let Some(LDestructRest::Keep(s)) = rest {273 f(*s);274 }275 for d in end {276 d.each_slot(f);277 }278 }279 #[cfg(feature = "exp-destruct")]280 Self::Object { fields, rest } => {281 for field in fields {282 if let Some(into) = &field.into {283 into.each_slot(f);284 } else {285 unreachable!("shorthand object destruct must store `into`");286 }287 }288 if let Some(LDestructRest::Keep(s)) = rest {289 f(*s);290 }291 }292 }293 }294295 pub fn slots(&self) -> SmallVec<[LocalSlot; 1]> {296 let mut out = SmallVec::new();297 self.each_slot(&mut |s| out.push(s));298 out299 }300}301302#[derive(Debug, Acyclic)]303pub struct LSliceExpr {304 pub value: LExpr,305 pub start: Option<LExpr>,306 pub end: Option<LExpr>,307 pub step: Option<LExpr>,308}309310#[derive(Debug, Acyclic)]311pub struct LArgsDesc {312 pub unnamed: Vec<Rc<LExpr>>,313 pub names: Vec<IStr>,314 pub values: Vec<Rc<LExpr>>,315}316317#[derive(Debug, Acyclic)]318pub struct LAssertStmt {319 pub cond: Spanned<LExpr>,320 pub message: Option<LExpr>,321}322323#[derive(Debug, Acyclic)]324pub struct LIndexPart {325 pub span: Span,326 pub value: LExpr,327 #[cfg(feature = "exp-null-coaelse")]328 pub null_coaelse: bool,329}330331#[derive(Debug, Acyclic)]332pub enum LObjBody {333 MemberList(LObjMembers),334 ObjComp(Box<LObjComp>),335}336337#[derive(Debug, Acyclic)]338pub struct LObjMembers {339 pub frame_shape: ClosureShape,340 341 342 pub this: Option<LocalSlot>,343 344 pub set_dollar: bool,345 346 pub uses_super: bool,347348 pub locals: Rc<Vec<LBind>>,349 pub asserts: Option<Rc<LObjAsserts>>,350 pub fields: Vec<LFieldMember>,351}352353#[derive(Debug, Acyclic)]354pub struct LObjComp {355 pub frame_shape: Rc<ClosureShape>,356 pub this: Option<LocalSlot>,357 pub set_dollar: bool,358 pub uses_super: bool,359360 pub locals: Rc<Vec<LBind>>,361 pub field: LFieldMember,362 pub compspecs: Vec<LCompSpec>,363}364365#[derive(Debug, Acyclic)]366pub struct LFieldMember {367 pub name: LFieldName,368 pub plus: bool,369 pub visibility: Visibility,370 pub value: Rc<(ClosureShape, LExpr)>,371}372373#[derive(Debug, Acyclic)]374pub struct LClosure<T: Acyclic> {375 pub shape: ClosureShape,376 pub value: T,377}378379#[derive(Debug, Acyclic)]380pub struct LObjAsserts {381 pub shape: ClosureShape,382 pub asserts: Vec<LAssertStmt>,383}384385#[derive(Debug, Acyclic)]386pub enum LFieldName {387 Fixed(IStr),388 Dyn(LExpr),389}390impl LFieldName {391 fn function_name(&self) -> Option<IStr> {392 match self {393 LFieldName::Fixed(istr) => Some(istr.clone()),394 LFieldName::Dyn(_) => None,395 }396 }397}398399#[derive(Debug, Acyclic)]400pub struct LArrComp {401 pub value_shape: ClosureShape,402 pub value: Rc<LExpr>,403 pub compspecs: Vec<LCompSpec>,404}405406#[derive(Debug, Acyclic)]407pub enum LCompSpec {408 If(LExpr),409 For {410 frame_shape: ClosureShape,411 destruct: LDestruct,412 over: LExpr,413 414 loop_invariant: bool,415 },416 #[cfg(feature = "exp-object-iteration")]417 ForObj {418 frame_shape: ClosureShape,419 key: LocalSlot,420 visibility: jrsonnet_ir::Visibility,421 value: LDestruct,422 over: LExpr,423 loop_invariant: bool,424 },425}426427struct FrameAlloc<'s> {428 first_in_frame: LocalId,429 stack: &'s mut AnalysisStack,430 bomb: DropBomb,431}432impl<'s> FrameAlloc<'s> {433 fn new(stack: &'s mut AnalysisStack) -> Self {434 FrameAlloc {435 first_in_frame: stack.next_local_id(),436 stack,437 bomb: DropBomb::new("binding frame state"),438 }439 }440441 fn push_locals_closure(&mut self) -> ClosureOnStack {442 self.stack.push_closure_a(self.first_in_frame)443 }444445 fn define_local(&mut self, name: IStr, span: Option<Span>) -> Option<(LocalId, LocalSlot)> {446 let id = self.stack.next_local_id();447 let stack = self.stack.local_by_name.entry(name.clone()).or_default();448 if let Some(&existing) = stack.last()449 && !existing.defined_before(self.first_in_frame)450 {451 self.stack.report_error(452 format!("local is already defined in the current frame: {name}"),453 span,454 );455 return None;456 }457 stack.push(id);458 self.stack.local_defs.push(LocalDefinition {459 name,460 span,461 defined_at_depth: self.stack.depth,462 used_at_depth: u32::MAX,463 used_by_sibling: false,464 analysis: AnalysisResult::default(),465 analyzed: false,466 scratch_referenced: false,467 });468 let def = self.stack.defining_closure_mut();469 Some((id, def.define_local(id)))470 }471 fn alloc_bind(&mut self, bind: &BindSpec) -> Option<LDestruct> {472 match bind {473 BindSpec::Field { into, .. } => self.alloc_destruct(into),474 BindSpec::Function { name, .. } => {475 let (_, id) = self.define_local(name.clone(), None)?;476 Some(LDestruct::Full(id))477 }478 }479 }480 fn alloc_destruct(&mut self, destruct: &Destruct) -> Option<LDestruct> {481 Some(match destruct {482 Destruct::Full(name) => {483 let (_, id) = self.define_local(name.value.clone(), Some(name.span.clone()))?;484 LDestruct::Full(id)485 }486 #[cfg(feature = "exp-destruct")]487 Destruct::Skip => LDestruct::Skip,488 #[cfg(feature = "exp-destruct")]489 Destruct::Array { start, rest, end } => {490 let start = start491 .iter()492 .map(|d| self.alloc_destruct(d))493 .collect::<Option<Vec<_>>>()?;494 let rest = match rest {495 Some(jrsonnet_ir::DestructRest::Keep(name)) => {496 let (_, id) = self.define_local(name.clone(), None)?;497 Some(LDestructRest::Keep(id))498 }499 Some(jrsonnet_ir::DestructRest::Drop) => Some(LDestructRest::Drop),500 None => None,501 };502 let end = end503 .iter()504 .map(|d| self.alloc_destruct(d))505 .collect::<Option<Vec<_>>>()?;506 LDestruct::Array { start, rest, end }507 }508 #[cfg(feature = "exp-destruct")]509 Destruct::Object { fields, rest } => {510 let mut l_fields: Vec<(IStr, LDestruct)> = Vec::with_capacity(fields.len());511 512 for (name, into, _default) in fields {513 let into = if let Some(inner) = into {514 self.alloc_destruct(inner)?515 } else {516 let (_, id) = self.define_local(name.clone(), None)?;517 LDestruct::Full(id)518 };519 l_fields.push((name.clone(), into));520 }521 522 let l_fields: Vec<LDestructField> = l_fields523 .into_iter()524 .zip(fields.iter())525 .map(|((name, into), (_n, _i, default))| {526 let default = match default {527 Some(e) => {528 let mut default_taint = AnalysisResult::default();529 Some(self.stack.in_using_closure(|stack| {530 Rc::new(analyze(&e.value, stack, &mut default_taint))531 }))532 }533 None => None,534 };535 LDestructField {536 name,537 into: Some(into),538 default,539 }540 })541 .collect();542 let rest = match rest {543 Some(jrsonnet_ir::DestructRest::Keep(name)) => {544 let (_, id) = self.define_local(name.clone(), None)?;545 Some(LDestructRest::Keep(id))546 }547 Some(jrsonnet_ir::DestructRest::Drop) => Some(LDestructRest::Drop),548 None => None,549 };550 LDestruct::Object {551 fields: l_fields,552 rest,553 }554 }555 })556 }557558 fn finish(self) -> PendingInit<'s> {559 let Self {560 first_in_frame,561 stack,562 bomb,563 } = self;564 let first_after_frame = stack.next_local_id();565 PendingInit {566 first_after_frame,567 stack,568 closures: Closures {569 referenced: vec![],570 spec_shapes: vec![],571 first_in_frame,572 },573 bomb,574 }575 }576}577578579struct PendingInit<'s> {580 first_after_frame: LocalId,581 stack: &'s mut AnalysisStack,582 closures: Closures,583 bomb: DropBomb,584}585586impl<'s> PendingInit<'s> {587 588 589 590 fn record_spec_init(&mut self, destruct: &LDestruct, analysis: AnalysisResult) {591 let mut refs: SmallVec<[LocalId; 4]> = SmallVec::new();592 for i in self.closures.first_in_frame.0..self.first_after_frame.0 {593 let def = &mut self.stack.local_defs[i as usize];594 if def.scratch_referenced {595 refs.push(LocalId(i));596 def.scratch_referenced = false;597 }598 }599600 let mut ids_count = 0;601 let first_local = self.stack.top_defining_local();602 destruct.each_slot(&mut |slot| {603 ids_count += 1;604 let id = LocalId(first_local.0 + u32::from(slot.0));605 let def = &mut self.stack.local_defs[id.idx()];606 debug_assert!(!def.analyzed, "sanity: local {:?} analysed twice", def.name);607 def.analysis = analysis;608 def.analyzed = true;609 });610 self.closures.push_spec(ids_count, &refs);611 }612 613 614 fn finish(self) -> PendingBody<'s> {615 let Self {616 first_after_frame,617 closures,618 stack,619 bomb,620 } = self;621622 debug_assert_eq!(623 first_after_frame,624 stack.next_local_id(),625 "frame initialisation left unfinished locals"626 );627628 debug_assert_eq!(629 closures.spec_shapes.iter().map(|(_, d)| *d).sum::<usize>(),630 (first_after_frame.0 - closures.first_in_frame.0) as usize,631 "closures destruct-id counts must match frame local count"632 );633634 let mut changed = true;635 while changed {636 changed = false;637 for spec in closures.iter_specs() {638 for id_raw in spec.ids.clone() {639 let user = LocalId(id_raw);640 for &used in spec.references {641 changed |= stack.propagate_analysis(user, used);642 }643 }644 }645 }646647 stack.depth += 1;648 PendingBody {649 first_after_frame,650 closures,651 stack,652 bomb,653 }654 }655}656657658struct PendingBody<'s> {659 first_after_frame: LocalId,660 closures: Closures,661 stack: &'s mut AnalysisStack,662 bomb: DropBomb,663}664impl PendingBody<'_> {665 666 667 fn finish(self) {668 let PendingBody {669 first_after_frame,670 closures,671 stack,672 mut bomb,673 } = self;674 bomb.defuse();675 stack.depth -= 1;676677 debug_assert_eq!(678 first_after_frame,679 stack.next_local_id(),680 "nested scopes must be popped before outer frames"681 );682683 let mut changed = true;684 while changed {685 changed = false;686 for spec in closures.iter_specs() {687 688 let mut min_used_at = u32::MAX;689 for id_raw in spec.ids.clone() {690 min_used_at = min_used_at.min(stack.local_defs[id_raw as usize].used_at_depth);691 }692 if min_used_at == u32::MAX {693 continue;694 }695 for &used in spec.references {696 let used_def = &mut stack.local_defs[used.idx()];697 if min_used_at < used_def.used_at_depth {698 used_def.used_at_depth = min_used_at;699 changed = true;700 }701 }702 }703 }704705 let drained: Vec<LocalDefinition> = stack706 .local_defs707 .drain(closures.first_in_frame.idx()..)708 .collect();709 for (i, def) in drained.iter().enumerate().rev() {710 let id = LocalId(closures.first_in_frame.0 + arridx(i));711 let stack_locals = stack712 .local_by_name713 .get_mut(&def.name)714 .expect("local must be in name map");715 let popped = stack_locals.pop().expect("name stack should not be empty");716 debug_assert_eq!(popped, id, "name stack integrity");717 if stack_locals.is_empty() {718 stack.local_by_name.remove(&def.name);719 }720721 if def.used_at_depth == u32::MAX {722 if def.used_by_sibling {723 stack.report_warning(724 format!("local is only referenced by unused siblings: {}", def.name),725 def.span.clone(),726 );727 } else {728 stack.report_warning(format!("unused local: {}", def.name), def.span.clone());729 }730 } else if def.analysis.local_dependent_depth > def.defined_at_depth731 && def.analysis.object_dependent_depth > def.defined_at_depth732 && def.defined_at_depth != 0733 {734 735 736 stack.report_warning(737 format!("local could be hoisted to an outer scope: {}", def.name),738 def.span.clone(),739 );740 }741 }742 }743}744745struct Closures {746 747 748 749 750 751 752 referenced: Vec<LocalId>,753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 spec_shapes: Vec<(usize, usize)>,769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 first_in_frame: LocalId,805}806807struct Closure<'a> {808 references: &'a [LocalId],809 ids: std::ops::Range<u32>,810}811812impl Closures {813 fn push_spec(&mut self, destruct_ids_count: usize, refs: &[LocalId]) {814 self.referenced.extend_from_slice(refs);815 self.spec_shapes.push((refs.len(), destruct_ids_count));816 }817818 fn iter_specs(&self) -> impl Iterator<Item = Closure<'_>> {819 let mut refs = self.referenced.as_slice();820 let mut next_id = self.first_in_frame.0;821 self.spec_shapes.iter().map(move |(refs_len, dest_count)| {822 let (this_refs, rest) = refs.split_at(*refs_len);823 refs = rest;824 let start = next_id;825 next_id += arridx(*dest_count);826 Closure {827 references: this_refs,828 ids: start..next_id,829 }830 })831 }832}833834#[derive(Debug, Clone, Copy, PartialEq, Eq)]835pub enum DiagLevel {836 Error,837 Warning,838}839840#[derive(Debug, Clone, Acyclic)]841pub struct Diagnostic {842 pub level: DiagLevel,843 pub message: String,844 pub span: Option<Span>,845}846847struct DefiningClosure {848 first_local: LocalId,849 n_locals: u16,850}851852impl DefiningClosure {853 fn resolve(&self, target: LocalId) -> Option<LocalSlot> {854 let end = self.first_local.0 + u32::from(self.n_locals);855 if target.0 >= self.first_local.0 && target.0 < end {856 Some(LocalSlot(857 u16::try_from(target.0 - self.first_local.0).expect("local slots overflow"),858 ))859 } else {860 None861 }862 }863 fn define_local(&mut self, local: LocalId) -> LocalSlot {864 let slot = self.n_locals;865 let id = self.first_local.0 + u32::from(slot);866 debug_assert_eq!(local.0, id);867 self.n_locals = self.n_locals.checked_add(1).expect("local slots overflow");868 LocalSlot(slot)869 }870}871872873struct ClosureFrame {874 875 defining: Option<DefiningClosure>,876 877 captures: FxHashMap<LocalId, CaptureSlot>,878 879 capture_sources: Vec<LSlot>,880}881882#[allow(clippy::struct_excessive_bools)]883pub struct AnalysisStack {884 local_defs: Vec<LocalDefinition>,885 886 887 local_by_name: FxHashMap<IStr, SmallVec<[LocalId; 2]>>,888889 890 depth: u32,891 892 last_object_depth: u32,893 894 895 first_object_depth: u32,896897 898 this_local: Option<LocalId>,899 900 dollar_alias: Option<LocalId>,901 902 cur_self_used: bool,903 904 cur_super_used: bool,905 906 dollar_used: bool,907908 909 closure_stack: Vec<ClosureFrame>,910911 diagnostics: Vec<Diagnostic>,912 913 errored: bool,914}915916#[must_use]917struct ClosureOnStack {918 bomb: DropBomb,919}920921impl AnalysisStack {922 pub fn new() -> Self {923 Self {924 local_defs: Vec::new(),925 local_by_name: FxHashMap::default(),926 depth: 0,927 last_object_depth: u32::MAX,928 first_object_depth: u32::MAX,929 this_local: None,930 dollar_alias: None,931 cur_self_used: false,932 cur_super_used: false,933 dollar_used: false,934 closure_stack: Vec::new(),935 diagnostics: Vec::new(),936 errored: false,937 }938 }939940 fn push_root_closure(&mut self, externals: u16) -> ClosureOnStack {941 assert!(942 self.closure_stack.is_empty(),943 "root is only possible with empty stack"944 );945946 self.closure_stack.push(ClosureFrame {947 defining: Some(DefiningClosure {948 first_local: LocalId(0),949 n_locals: externals,950 }),951 captures: FxHashMap::default(),952 capture_sources: Vec::new(),953 });954955 ClosureOnStack {956 bomb: DropBomb::new("root closure"),957 }958 }959960 fn push_closure_a(&mut self, first_local: LocalId) -> ClosureOnStack {961 self.closure_stack.push(ClosureFrame {962 defining: Some(DefiningClosure {963 first_local,964 n_locals: 0,965 }),966 captures: FxHashMap::default(),967 capture_sources: Vec::new(),968 });969 ClosureOnStack {970 bomb: DropBomb::new("closure with locals"),971 }972 }973974 #[inline]975 fn in_using_closure<T>(976 &mut self,977 inner: impl FnOnce(&mut AnalysisStack) -> T,978 ) -> (ClosureShape, T) {979 fn push_closure_b(stack: &mut AnalysisStack) -> ClosureOnStack {980 stack.closure_stack.push(ClosureFrame {981 defining: None,982 captures: FxHashMap::default(),983 capture_sources: Vec::new(),984 });985 ClosureOnStack {986 bomb: DropBomb::new("closure with locals"),987 }988 }989 let closure = push_closure_b(self);990 let v = inner(self);991 let shape = self.pop_closure(closure);992 (shape, v)993 }994995 fn pop_closure(&mut self, mut closure: ClosureOnStack) -> ClosureShape {996 closure.bomb.defuse();997 let frame = self.closure_stack.pop().expect("closure frame");998 ClosureShape {999 captures: frame.capture_sources.into_boxed_slice(),1000 n_locals: frame.defining.map(|d| d.n_locals).unwrap_or_default(),1001 }1002 }10031004 1005 1006 1007 fn resolve_to_slot(&mut self, target: LocalId) -> LSlot {1008 let top = self.closure_stack.len();1009 debug_assert!(top > 0, "resolve_to_slot called with no closure frame");1010 Self::resolve_at(&mut self.closure_stack, top - 1, target)1011 }10121013 fn resolve_at(stack: &mut [ClosureFrame], idx: usize, target: LocalId) -> LSlot {1014 if let Some(def) = &stack[idx].defining {1015 if let Some(resolved) = def.resolve(target) {1016 return LSlot::Local(resolved);1017 }1018 } else {1019 1020 1021 for j in (0..idx).rev() {1022 if let Some(def) = &stack[j].defining {1023 if let Some(resolved) = def.resolve(target) {1024 return LSlot::Local(resolved);1025 }1026 break;1027 }1028 }1029 }1030 if let Some(&cap_idx) = stack[idx].captures.get(&target) {1031 return LSlot::Capture(cap_idx);1032 }1033 debug_assert!(idx > 0, "no enclosing closure frame for target {target:?}");1034 let parent_slot = Self::resolve_at(stack, idx - 1, target);1035 let frame = &mut stack[idx];1036 let cap_idx = CaptureSlot(1037 frame1038 .capture_sources1039 .len()1040 .try_into()1041 .expect("frame has more than u16::MAX captures"),1042 );1043 frame.capture_sources.push(parent_slot);1044 frame.captures.insert(target, cap_idx);1045 LSlot::Capture(cap_idx)1046 }10471048 fn next_local_id(&self) -> LocalId {1049 LocalId(arridx(self.local_defs.len()))1050 }10511052 fn report_error(&mut self, msg: impl Into<String>, span: Option<Span>) {1053 self.errored = true;1054 self.diagnostics.push(Diagnostic {1055 level: DiagLevel::Error,1056 message: msg.into(),1057 span,1058 });1059 }1060 fn report_warning(&mut self, msg: impl Into<String>, span: Option<Span>) {1061 self.diagnostics.push(Diagnostic {1062 level: DiagLevel::Warning,1063 message: msg.into(),1064 span,1065 });1066 }10671068 fn use_local(&mut self, name: &IStr, span: Span, taint: &mut AnalysisResult) -> Option<LSlot> {1069 let Some(ids) = self.local_by_name.get(name) else {1070 let names = suggest_names(name, self.local_by_name.keys());1071 self.report_error(1072 format!("undefined local: {name}{}", format_found(&names, "local")),1073 Some(span),1074 );1075 return None;1076 };1077 let id = *ids.last().expect("empty stacks should be removed");1078 let depth = self.depth;1079 let def = &mut self.local_defs[id.idx()];1080 def.use_at(depth);1081 taint.depend_on_local(def.defined_at_depth);1082 if def.analyzed {1083 taint.taint_by(def.analysis);1084 } else {1085 def.scratch_referenced = true;1086 }1087 Some(self.resolve_to_slot(id))1088 }10891090 1091 pub fn define_external_local(&mut self, name: IStr, id: LocalId) {1092 assert!(1093 self.local_defs.iter().all(|d| d.analyzed),1094 "external locals must be defined before the root expression is analysed"1095 );1096 assert_eq!(1097 id,1098 self.next_local_id(),1099 "external local id mismatch for {name} (externals must be defined in allocation order)"1100 );1101 self.local_defs.push(LocalDefinition {1102 name: name.clone(),1103 span: None,1104 defined_at_depth: 0,1105 used_at_depth: u32::MAX,1106 used_by_sibling: false,1107 analysis: AnalysisResult::default(),1108 analyzed: true,1109 scratch_referenced: false,1110 });1111 self.local_by_name.entry(name).or_default().push(id);1112 }11131114 fn defining_closure_mut(&mut self) -> &mut DefiningClosure {1115 self.closure_stack1116 .iter_mut()1117 .rev()1118 .find_map(|c| c.defining.as_mut())1119 .expect("no enclosing defining closure frame")1120 }1121 fn defining_closure(&self) -> &DefiningClosure {1122 self.closure_stack1123 .iter()1124 .rev()1125 .find_map(|c| c.defining.as_ref())1126 .expect("no enclosing defining closure frame")1127 }1128}11291130impl Default for AnalysisStack {1131 fn default() -> Self {1132 Self::new()1133 }1134}11351136impl AnalysisStack {1137 fn top_defining_local(&self) -> LocalId {1138 self.defining_closure().first_local1139 }11401141 1142 1143 1144 fn propagate_analysis(&mut self, user: LocalId, used: LocalId) -> bool {1145 let (used_analysis, used_defined_at_depth) = {1146 let u = &self.local_defs[used.idx()];1147 (u.analysis, u.defined_at_depth)1148 };1149 let user_def = &mut self.local_defs[user.idx()];1150 let before_obj = user_def.analysis.object_dependent_depth;1151 let before_loc = user_def.analysis.local_dependent_depth;1152 user_def.analysis.taint_by(used_analysis);1153 user_def.analysis.depend_on_local(used_defined_at_depth);1154 before_obj != user_def.analysis.object_dependent_depth1155 || before_loc != user_def.analysis.local_dependent_depth1156 }1157}11581159mod names {1160 use crate::names;11611162 names! {1163 this: "this",1164 }1165}116611671168impl AnalysisStack {1169 #[inline]1170 fn in_object_scope<T>(1171 &mut self,1172 inner: impl FnOnce(&mut AnalysisStack) -> T,1173 ) -> (ObjectUsage, ClosureShape, T) {1174 fn enter_object_scope(stack: &mut AnalysisStack) -> ObjectScope {1175 let is_outermost = stack.first_object_depth == u32::MAX;1176 let this_id = stack.next_local_id();1177 let closure = stack.push_closure_a(this_id);1178 let pushed = stack.push_pseudo_local(names::this());1179 debug_assert_eq!(pushed, this_id, "this pseudo-local id");1180 let scope = ObjectScope {1181 this_id,1182 is_outermost,1183 prev_this_local: stack.this_local,1184 prev_dollar_alias: stack.dollar_alias,1185 prev_cur_self_used: stack.cur_self_used,1186 prev_cur_super_used: stack.cur_super_used,1187 prev_dollar_used: is_outermost.then_some(stack.dollar_used),1188 prev_last_object: stack.last_object_depth,1189 prev_first_object: stack.first_object_depth,1190 closure,1191 };11921193 stack.this_local = Some(scope.this_id);1194 if is_outermost {1195 stack.dollar_alias = Some(scope.this_id);1196 stack.first_object_depth = stack.depth;1197 stack.dollar_used = false;1198 }1199 stack.last_object_depth = stack.depth;1200 stack.cur_self_used = false;1201 stack.cur_super_used = false;1202 scope1203 }12041205 fn leave_object_scope(1206 stack: &mut AnalysisStack,1207 scope: ObjectScope,1208 ) -> (ObjectUsage, ClosureShape) {1209 let ObjectScope {1210 this_id,1211 is_outermost,1212 prev_this_local,1213 prev_dollar_alias,1214 prev_cur_self_used,1215 prev_cur_super_used,1216 prev_dollar_used,1217 prev_last_object,1218 prev_first_object,1219 closure,1220 } = scope;1221 let _ = stack.local_defs.pop().expect("this pseudo-local exists");1222 debug_assert_eq!(stack.local_defs.len(), this_id.0 as usize);12231224 let set_dollar = is_outermost && stack.dollar_used;1225 let usage = ObjectUsage {1226 this_used: stack.cur_self_used || stack.cur_super_used || set_dollar,1227 uses_super: stack.cur_super_used,1228 set_dollar,1229 };12301231 stack.this_local = prev_this_local;1232 stack.dollar_alias = prev_dollar_alias;1233 stack.cur_self_used = prev_cur_self_used;1234 stack.cur_super_used = prev_cur_super_used;1235 if let Some(prev) = prev_dollar_used {1236 stack.dollar_used = prev;1237 }1238 stack.last_object_depth = prev_last_object;1239 stack.first_object_depth = prev_first_object;12401241 let frame_shape = stack.pop_closure(closure);1242 (usage, frame_shape)1243 }1244 let scope = enter_object_scope(self);1245 let v = inner(self);1246 let (usage, shape) = leave_object_scope(self, scope);1247 (usage, shape, v)1248 }12491250 fn push_pseudo_local(&mut self, name: IStr) -> LocalId {1251 let id = self.next_local_id();1252 self.local_defs.push(LocalDefinition {1253 name,1254 span: None,1255 defined_at_depth: self.depth,1256 used_at_depth: u32::MAX,1257 used_by_sibling: false,1258 analysis: AnalysisResult::default(),1259 analyzed: true,1260 scratch_referenced: false,1261 });1262 {1263 let def = self.defining_closure_mut();1264 let _ = def.define_local(id);1265 }1266 id1267 }12681269 fn use_this(&mut self, taint: &mut AnalysisResult) -> Option<LSlot> {1270 let id = self.this_local?;1271 self.cur_self_used = true;1272 self.use_pseudo_local(id, taint);1273 Some(self.resolve_to_slot(id))1274 }12751276 fn use_super(&mut self, taint: &mut AnalysisResult) -> Option<()> {1277 let id = self.this_local?;1278 self.cur_super_used = true;1279 self.use_pseudo_local(id, taint);1280 Some(())1281 }12821283 fn use_dollar(&mut self, taint: &mut AnalysisResult) -> Option<LSlot> {1284 let id = self.dollar_alias?;1285 self.dollar_used = true;1286 self.use_pseudo_local(id, taint);1287 Some(self.resolve_to_slot(id))1288 }12891290 1291 fn use_pseudo_local(&mut self, id: LocalId, taint: &mut AnalysisResult) {1292 let depth = self.depth;1293 let def = &mut self.local_defs[id.idx()];1294 def.use_at(depth);1295 taint.depend_on_local(def.defined_at_depth);1296 taint.depend_on_object(def.defined_at_depth);1297 }1298}12991300#[must_use]1301struct ObjectScope {1302 this_id: LocalId,1303 is_outermost: bool,1304 prev_this_local: Option<LocalId>,1305 prev_dollar_alias: Option<LocalId>,1306 prev_cur_self_used: bool,1307 prev_cur_super_used: bool,1308 prev_dollar_used: Option<bool>,1309 prev_last_object: u32,1310 prev_first_object: u32,1311 closure: ClosureOnStack,1312}13131314struct ObjectUsage {1315 this_used: bool,1316 uses_super: bool,1317 set_dollar: bool,1318}13191320fn analyze_assert(1321 stmt: &AssertStmt,1322 stack: &mut AnalysisStack,1323 taint: &mut AnalysisResult,1324) -> LAssertStmt {1325 let cond = analyze(&stmt.assertion.value, stack, taint);1326 let message = stmt.message.as_ref().map(|m| analyze(m, stack, taint));1327 LAssertStmt {1328 cond: Spanned::new(cond, stmt.assertion.span.clone()),1329 message,1330 }1331}13321333#[allow(clippy::too_many_lines)]1334pub fn analyze_named(1335 name: IStr,1336 expr: &Expr,1337 stack: &mut AnalysisStack,1338 taint: &mut AnalysisResult,1339) -> LExpr {1340 if let Expr::Function(params, body) = expr {1341 return analyze_function(Some(name), params, body, stack, taint);1342 }1343 analyze(expr, stack, taint)1344}1345#[allow(clippy::too_many_lines)]1346pub fn analyze(expr: &Expr, stack: &mut AnalysisStack, taint: &mut AnalysisResult) -> LExpr {1347 match expr {1348 Expr::Literal(l) => match l {1349 LiteralType::This => stack.use_this(taint).map_or_else(1350 || {1351 stack.report_error("`self` used outside of object", None);1352 LExpr::BadLocal("self")1353 },1354 LExpr::Slot,1355 ),1356 LiteralType::Super => {1357 if stack.use_super(taint).is_some() {1358 LExpr::Super1359 } else {1360 stack.report_error("`super` used outside of object", None);1361 LExpr::BadLocal("super")1362 }1363 }1364 LiteralType::Dollar => stack.use_dollar(taint).map_or_else(1365 || {1366 stack.report_error("`$` used outside of object", None);1367 LExpr::BadLocal("$")1368 },1369 LExpr::Slot,1370 ),1371 LiteralType::Null => LExpr::Null,1372 LiteralType::True => LExpr::Bool(true),1373 LiteralType::False => LExpr::Bool(false),1374 },1375 Expr::Str(s) => LExpr::Str(s.clone()),1376 Expr::Num(n) => LExpr::Num(*n),1377 Expr::Var(v) => stack1378 .use_local(&v.value, v.span.clone(), taint)1379 .map_or_else(|| LExpr::BadLocal("ref"), LExpr::Slot),1380 Expr::Arr(a) => {1381 let (shape, items) = stack1382 .in_using_closure(|stack| a.iter().map(|v| analyze(v, stack, taint)).collect());1383 LExpr::Arr {1384 shape,1385 items: Rc::new(items),1386 }1387 }1388 Expr::ArrComp(inner, comp) => analyze_arr_comp(inner, comp, stack, taint),1389 Expr::Obj(obj) => LExpr::Obj(analyze_obj_body(obj, stack, taint)),1390 Expr::ObjExtend(base, obj) => LExpr::ObjExtend(1391 Box::new(analyze(base, stack, taint)),1392 analyze_obj_body(obj, stack, taint),1393 ),1394 Expr::UnaryOp(op, value) => LExpr::UnaryOp(*op, Box::new(analyze(value, stack, taint))),1395 Expr::BinaryOp(op) => {1396 let BinaryOp {1397 lhs,1398 op: optype,1399 rhs,1400 } = &**op;1401 LExpr::BinaryOp {1402 lhs: Box::new(analyze(lhs, stack, taint)),1403 op: *optype,1404 rhs: Box::new(analyze(rhs, stack, taint)),1405 }1406 }1407 Expr::AssertExpr(assert) => {1408 let AssertExpr { assert, rest } = &**assert;1409 let assert = Rc::new(analyze_assert(assert, stack, taint));1410 let rest = Box::new(analyze(rest, stack, taint));1411 LExpr::AssertExpr { assert, rest }1412 }1413 Expr::LocalExpr(binds, body) => analyze_local_expr(binds, body, stack, taint),1414 Expr::Import(kind, path_expr) => {1415 let Expr::Str(path) = &**path_expr else {1416 stack.report_error(1417 "import path must be a string literal",1418 Some(kind.span.clone()),1419 );1420 return LExpr::BadLocal("bad import");1421 };1422 LExpr::Import {1423 kind: kind.clone(),1424 kind_span: kind.span.clone(),1425 path: path.clone(),1426 }1427 }1428 Expr::ErrorStmt(span, inner) => {1429 LExpr::Error(span.clone(), Box::new(analyze(inner, stack, taint)))1430 }1431 Expr::Apply(applicable, args, tailstrict) => {1432 let app = analyze(applicable, stack, taint);1433 let ArgsDesc {1434 unnamed,1435 names,1436 values,1437 } = &args.value;1438 let unnamed_l = unnamed1439 .iter()1440 .map(|a| Rc::new(analyze(a, stack, taint)))1441 .collect();1442 let values_l = values1443 .iter()1444 .map(|a| Rc::new(analyze(a, stack, taint)))1445 .collect();1446 LExpr::Apply {1447 applicable: Box::new(app),1448 args: Spanned::new(1449 LArgsDesc {1450 unnamed: unnamed_l,1451 names: names.clone(),1452 values: values_l,1453 },1454 args.span.clone(),1455 ),1456 tailstrict: *tailstrict,1457 }1458 }1459 Expr::Index { indexable, parts } => {1460 let idx = analyze(indexable, stack, taint);1461 let parts_l = parts1462 .iter()1463 .map(|p| {1464 let value = analyze(&p.value, stack, taint);1465 LIndexPart {1466 span: p.span.clone(),1467 value,1468 #[cfg(feature = "exp-null-coaelse")]1469 null_coaelse: p.null_coaelse,1470 }1471 })1472 .collect();1473 LExpr::Index {1474 indexable: Box::new(idx),1475 parts: parts_l,1476 }1477 }1478 Expr::Function(params, body) => analyze_function(None, params, body, stack, taint),1479 Expr::IfElse(ifelse) => {1480 let IfElse {1481 cond,1482 cond_then,1483 cond_else,1484 } = &**ifelse;1485 let cond_l = analyze(&cond.cond, stack, taint);1486 let then_l = analyze(cond_then, stack, taint);1487 let else_l = cond_else1488 .as_ref()1489 .map(|e| Box::new(analyze(e, stack, taint)));1490 LExpr::IfElse {1491 cond: Box::new(cond_l),1492 cond_then: Box::new(then_l),1493 cond_else: else_l,1494 }1495 }1496 Expr::Slice(slice) => {1497 let Slice {1498 value,1499 slice: SliceDesc { start, end, step },1500 } = &**slice;1501 let value_l = analyze(value, stack, taint);1502 let start_l = start.as_ref().map(|e| analyze(&e.value, stack, taint));1503 let end_l = end.as_ref().map(|e| analyze(&e.value, stack, taint));1504 let step_l = step.as_ref().map(|e| analyze(&e.value, stack, taint));1505 LExpr::Slice(Box::new(LSliceExpr {1506 value: value_l,1507 start: start_l,1508 end: end_l,1509 step: step_l,1510 }))1511 }1512 }1513}15141515fn analyze_local_expr(1516 binds: &[BindSpec],1517 body: &Expr,1518 stack: &mut AnalysisStack,1519 taint: &mut AnalysisResult,1520) -> LExpr {1521 if binds.is_empty() {1522 return analyze(body, stack, taint);1523 }1524 let frame_start = stack.next_local_id();1525 let closure = stack.push_closure_a(frame_start);1526 let (l_binds, body_expr) = process_local_frame(binds, stack, taint, |stack, taint| {1527 analyze(body, stack, taint)1528 });1529 let frame_shape = stack.pop_closure(closure);1530 LExpr::LocalExpr(Box::new(LLocalExpr {1531 frame_shape,1532 binds: l_binds,1533 body: body_expr,1534 }))1535}15361537fn analyze_bind_value(1538 bind: &BindSpec,1539 stack: &mut AnalysisStack,1540 taint: &mut AnalysisResult,1541) -> LExpr {1542 match bind {1543 BindSpec::Field {1544 value: Expr::Function(params, value),1545 into: Destruct::Full(name),1546 } => analyze_function(Some(name.value.clone()), params, value, stack, taint),1547 BindSpec::Field { value, .. } => analyze(value, stack, taint),1548 BindSpec::Function {1549 params,1550 value,1551 name,1552 } => analyze_function(Some(name.clone()), params, value, stack, taint),1553 }1554}15551556fn process_local_frame<R>(1557 binds: &[BindSpec],1558 stack: &mut AnalysisStack,1559 taint: &mut AnalysisResult,1560 body_fn: impl FnOnce(&mut AnalysisStack, &mut AnalysisResult) -> R,1561) -> (Vec<LBind>, R) {1562 let mut alloc = FrameAlloc::new(stack);15631564 let mut destructs: Vec<Option<LDestruct>> = Vec::with_capacity(binds.len());1565 for bind in binds {1566 destructs.push(alloc.alloc_bind(bind));1567 }1568 let mut pending = alloc.finish();15691570 let mut l_binds: Vec<LBind> = Vec::with_capacity(binds.len());1571 for (bind, destruct) in binds.iter().zip(destructs) {1572 let mut value_taint = AnalysisResult::default();1573 let (value_shape, value) = pending1574 .stack1575 .in_using_closure(|stack| analyze_bind_value(bind, stack, &mut value_taint));1576 taint.taint_by(value_taint);1577 if let Some(destruct) = destruct {1578 pending.record_spec_init(&destruct, value_taint);1579 l_binds.push(LBind {1580 destruct,1581 value_shape,1582 value: Rc::new(value),1583 });1584 } else {1585 pending.closures.push_spec(0, &[]);1586 }1587 }15881589 let body_frame = pending.finish();1590 let result = body_fn(body_frame.stack, taint);1591 body_frame.finish();15921593 (l_binds, result)1594}15951596fn analyze_function(1597 name: Option<IStr>,1598 params: &ExprParams,1599 body: &Expr,1600 stack: &mut AnalysisStack,1601 taint: &mut AnalysisResult,1602) -> LExpr {1603 let mut alloc = FrameAlloc::new(stack);1604 let closure = alloc.push_locals_closure();16051606 let mut param_destructs: Vec<Option<LDestruct>> = Vec::with_capacity(params.exprs.len());1607 for p in ¶ms.exprs {1608 param_destructs.push(alloc.alloc_destruct(&p.destruct));1609 }16101611 let mut pending = alloc.finish();16121613 let mut l_params: Vec<LParam> = Vec::with_capacity(params.exprs.len());1614 for (p, destruct) in params.exprs.iter().zip(param_destructs) {1615 let mut value_taint = AnalysisResult::default();1616 let default = p.default.as_ref().map_or_else(1617 || None,1618 |d| {1619 Some(1620 pending1621 .stack1622 .in_using_closure(|stack| Rc::new(analyze(d, stack, &mut value_taint))),1623 )1624 },1625 );1626 taint.taint_by(value_taint);1627 if let Some(destruct) = destruct {1628 let name = match &p.destruct {1629 Destruct::Full(n) => Some(n.value.clone()),1630 #[cfg(feature = "exp-destruct")]1631 _ => None,1632 };1633 pending.record_spec_init(&destruct, value_taint);1634 l_params.push(LParam {1635 name,1636 destruct,1637 default,1638 });1639 } else {1640 pending.closures.push_spec(0, &[]);1641 }1642 }16431644 let body_frame = pending.finish();1645 let body_expr = analyze(body, body_frame.stack, taint);1646 body_frame.finish();1647 let body_shape = stack.pop_closure(closure);16481649 1650 if l_params.len() == 1 && l_params[0].default.is_none() {1651 stack.report_warning(1652 "do not define identity functions manually, use std.id instead",1653 None,1654 );1655 #[allow(irrefutable_let_patterns, reason = "refutable with exp-destruct")]1656 if let LDestruct::Full(param_slot) = &l_params[0].destruct1657 && let LExpr::Slot(LSlot::Local(s)) = &body_expr1658 && s == param_slot1659 {1660 return LExpr::IdentityFunction {};1661 }1662 }16631664 LExpr::Function(Rc::new(LFunction {1665 name,1666 params: l_params,1667 signature: params.signature.clone(),1668 body_shape,1669 body: Rc::new(body_expr),1670 }))1671}16721673fn analyze_obj_body(1674 obj: &ObjBody,1675 stack: &mut AnalysisStack,1676 taint: &mut AnalysisResult,1677) -> LObjBody {1678 match obj {1679 ObjBody::MemberList(members) => {1680 LObjBody::MemberList(analyze_obj_members(members, stack, taint))1681 }1682 ObjBody::ObjComp(comp) => LObjBody::ObjComp(Box::new(analyze_obj_comp(comp, stack, taint))),1683 }1684}16851686fn analyze_obj_members(1687 members: &ObjMembers,1688 stack: &mut AnalysisStack,1689 taint: &mut AnalysisResult,1690) -> LObjMembers {1691 let ObjMembers {1692 locals,1693 asserts,1694 fields,1695 } = members;16961697 1698 let field_names: Vec<LFieldName> = fields1699 .iter()1700 .map(|f| match &f.name.value {1701 FieldName::Fixed(s) => LFieldName::Fixed(s.clone()),1702 FieldName::Dyn(e) => LFieldName::Dyn(analyze(e, stack, taint)),1703 })1704 .collect();17051706 let (usage, frame_shape, (l_binds, (l_asserts_opt, l_fields))) =1707 stack.in_object_scope(|stack| {1708 process_local_frame(locals, stack, taint, |stack, taint| {1709 let l_asserts_opt = if asserts.is_empty() {1710 None1711 } else {1712 let (shape, l_asserts) = stack.in_using_closure(|stack| {1713 let mut l_asserts = Vec::with_capacity(asserts.len());1714 for a in asserts {1715 let mut assert_taint = AnalysisResult::default();1716 l_asserts.push(analyze_assert(a, stack, &mut assert_taint));1717 taint.taint_by(assert_taint);1718 }1719 l_asserts1720 });1721 Some(Rc::new(LObjAsserts {1722 shape,1723 asserts: l_asserts,1724 }))1725 };1726 let mut l_fields = Vec::with_capacity(fields.len());1727 for (f, name) in fields.iter().zip(field_names) {1728 let value = stack.in_using_closure(|stack| {1729 if let Some(params) = &f.params {1730 analyze_function(name.function_name(), params, &f.value, stack, taint)1731 } else {1732 analyze(&f.value, stack, taint)1733 }1734 });1735 l_fields.push(LFieldMember {1736 name,1737 plus: f.plus,1738 visibility: f.visibility,1739 value: Rc::new(value),1740 });1741 }1742 (l_asserts_opt, l_fields)1743 })1744 });1745 1746 1747 let this_slot = usage.this_used.then_some(LocalSlot(0));1748 LObjMembers {1749 frame_shape,1750 this: this_slot,1751 set_dollar: usage.set_dollar,1752 uses_super: usage.uses_super,1753 locals: Rc::new(l_binds),1754 asserts: l_asserts_opt,1755 fields: l_fields,1756 }1757}17581759fn analyze_obj_comp(1760 comp: &ObjComp,1761 stack: &mut AnalysisStack,1762 taint: &mut AnalysisResult,1763) -> LObjComp {1764 let res = analyze_comp_specs(&comp.compspecs, stack, taint, |stack, taint| {1765 let field_name = match &comp.field.name.value {1766 FieldName::Fixed(s) => LFieldName::Fixed(s.clone()),1767 FieldName::Dyn(e) => LFieldName::Dyn(analyze(e, stack, taint)),1768 };17691770 let (usage, frame_shape, body) = stack.in_object_scope(|stack| {1771 process_local_frame(&comp.locals, stack, taint, |stack, taint| {1772 let value = stack.in_using_closure(|stack| {1773 if let Some(params) = &comp.field.params {1774 analyze_function(None, params, &comp.field.value, stack, taint)1775 } else {1776 analyze(&comp.field.value, stack, taint)1777 }1778 });1779 LFieldMember {1780 name: field_name,1781 plus: comp.field.plus,1782 visibility: comp.field.visibility,1783 value: Rc::new(value),1784 }1785 })1786 });1787 (usage, frame_shape, body)1788 });1789 let (usage, frame_shape, (locals, field)) = res.inner;1790 let this_slot = usage.this_used.then_some(LocalSlot(0));1791 LObjComp {1792 frame_shape: Rc::new(frame_shape),1793 this: this_slot,1794 set_dollar: usage.set_dollar,1795 uses_super: usage.uses_super,1796 locals: Rc::new(locals),1797 field,1798 compspecs: res.compspecs,1799 }1800}18011802fn analyze_arr_comp(1803 inner: &Expr,1804 specs: &[CompSpec],1805 stack: &mut AnalysisStack,1806 taint: &mut AnalysisResult,1807) -> LExpr {1808 let res = analyze_comp_specs(specs, stack, taint, |stack, taint| {1809 stack.in_using_closure(|stack| analyze(inner, stack, taint))1810 });1811 let (value_shape, value) = res.inner;1812 LExpr::ArrComp(Box::new(LArrComp {1813 value_shape,1814 value: Rc::new(value),1815 compspecs: res.compspecs,1816 }))1817}18181819fn analyze_comp_specs<R>(1820 specs: &[CompSpec],1821 stack: &mut AnalysisStack,1822 taint: &mut AnalysisResult,1823 inside: impl FnOnce(&mut AnalysisStack, &mut AnalysisResult) -> R,1824) -> CompSpecResult<R> {1825 fn go<R>(1826 idx: usize,1827 specs: &[CompSpec],1828 outer_depth: u32,1829 stack: &mut AnalysisStack,1830 taint: &mut AnalysisResult,1831 inside: impl FnOnce(&mut AnalysisStack, &mut AnalysisResult) -> R,1832 ) -> (R, Vec<LCompSpec>) {1833 if idx >= specs.len() {1834 return (inside(stack, taint), Vec::new());1835 }1836 match &specs[idx] {1837 CompSpec::IfSpec(IfSpecData { cond, .. }) => {1838 let cond_l = analyze(cond, stack, taint);1839 let (r, mut rest) = go(idx + 1, specs, outer_depth, stack, taint, inside);1840 rest.insert(0, LCompSpec::If(cond_l));1841 (r, rest)1842 }1843 CompSpec::ForSpec(ForSpecData { destruct, over }) => {1844 let mut over_taint = AnalysisResult::default();1845 let over_l = analyze(over, stack, &mut over_taint);1846 let loop_invariant = over_taint.local_dependent_depth > outer_depth;1847 taint.taint_by(over_taint);18481849 let mut alloc = FrameAlloc::new(stack);1850 let closure = alloc.push_locals_closure();1851 let Some(l_destruct) = alloc.alloc_destruct(destruct) else {1852 stack.pop_closure(closure);1853 return go(idx + 1, specs, outer_depth, stack, taint, inside);1854 };1855 let mut pending = alloc.finish();18561857 let var_analysis = AnalysisResult::default();1858 pending.record_spec_init(&l_destruct, var_analysis);18591860 let body_frame = pending.finish();1861 let (r, mut rest) =1862 go(idx + 1, specs, outer_depth, body_frame.stack, taint, inside);1863 body_frame.finish();1864 let frame_shape = stack.pop_closure(closure);18651866 rest.insert(1867 0,1868 LCompSpec::For {1869 frame_shape,1870 destruct: l_destruct,1871 over: over_l,1872 loop_invariant,1873 },1874 );1875 (r, rest)1876 }1877 #[cfg(feature = "exp-object-iteration")]1878 CompSpec::ForObjSpec(data) => {1879 let mut over_taint = AnalysisResult::default();1880 let over_l = analyze(&data.over, stack, &mut over_taint);1881 let loop_invariant = over_taint.local_dependent_depth > outer_depth;1882 taint.taint_by(over_taint);18831884 let mut alloc = FrameAlloc::new(stack);1885 let closure = alloc.push_locals_closure();1886 let Some((_, key_slot)) = alloc.define_local(data.key.clone(), None) else {1887 stack.pop_closure(closure);1888 return go(idx + 1, specs, outer_depth, stack, taint, inside);1889 };1890 let Some(l_value) = alloc.alloc_destruct(&data.value) else {1891 stack.pop_closure(closure);1892 return go(idx + 1, specs, outer_depth, stack, taint, inside);1893 };1894 let mut pending = alloc.finish();18951896 let var_analysis = AnalysisResult::default();1897 pending.record_spec_init(&LDestruct::Full(key_slot), var_analysis);1898 pending.record_spec_init(&l_value, var_analysis);18991900 let body_frame = pending.finish();1901 let (r, mut rest) =1902 go(idx + 1, specs, outer_depth, body_frame.stack, taint, inside);1903 body_frame.finish();1904 let frame_shape = stack.pop_closure(closure);19051906 rest.insert(1907 0,1908 LCompSpec::ForObj {1909 frame_shape,1910 key: key_slot,1911 visibility: data.visibility,1912 value: l_value,1913 over: over_l,1914 loop_invariant,1915 },1916 );1917 (r, rest)1918 }1919 }1920 }1921 let outer_depth = stack.depth;1922 let (r, compspecs) = go(0, specs, outer_depth, stack, taint, inside);1923 CompSpecResult {1924 inner: r,1925 compspecs,1926 }1927}19281929struct CompSpecResult<R> {1930 inner: R,1931 compspecs: Vec<LCompSpec>,1932}19331934pub fn analyze_root(expr: &Expr, ctx: Vec<(IStr, LocalId)>) -> AnalysisReport {1935 let mut stack = AnalysisStack::new();1936 for (name, id) in ctx {1937 stack.define_external_local(name, id);1938 }19391940 let externals_count: u16 = stack1941 .local_defs1942 .len()1943 .try_into()1944 .expect("more than u16::MAX externals");1945 let closure = stack.push_root_closure(externals_count);19461947 let mut taint = AnalysisResult::default();1948 let lir = analyze(expr, &mut stack, &mut taint);19491950 let root_shape = stack.pop_closure(closure);1951 debug_assert!(1952 stack.closure_stack.is_empty(),1953 "closure stack imbalance after analyze"1954 );19551956 AnalysisReport {1957 lir,1958 root_shape,1959 root_analysis: taint,1960 diagnostics_list: stack.diagnostics,1961 errored: stack.errored,1962 }1963}19641965pub struct AnalysisReport {1966 pub lir: LExpr,1967 pub root_shape: ClosureShape,1968 pub root_analysis: AnalysisResult,1969 pub diagnostics_list: Vec<Diagnostic>,1970 pub errored: bool,1971}19721973#[cfg(test)]1974mod tests {1975 #[test]1976 #[cfg(not(feature = "exp-null-coaelse"))]1977 fn snapshots() {1978 use std::fs;19791980 use insta::{assert_snapshot, glob};1981 use jrsonnet_ir::Source;19821983 use super::*;19841985 fn render_diagnostics(src: &str, diags: &[Diagnostic]) -> String {1986 use std::fmt::Write;19871988 use hi_doc::{Formatting, SnippetBuilder, Text};19891990 let mut out = String::new();1991 let mut unspanned = Vec::new();1992 let mut spanned: Vec<&Diagnostic> = Vec::new();1993 for d in diags {1994 if d.span.is_some() {1995 spanned.push(d);1996 } else {1997 unspanned.push(d);1998 }1999 }2000 if !spanned.is_empty() {2001 let mut builder = SnippetBuilder::new(src);2002 for d in spanned {2003 let span = d.span.as_ref().expect("spanned");2004 let ab = match d.level {2005 DiagLevel::Error => {2006 builder.error(Text::fragment(d.message.clone(), Formatting::default()))2007 }2008 DiagLevel::Warning => builder2009 .warning(Text::fragment(d.message.clone(), Formatting::default())),2010 };2011 ab.range(span.range()).build();2012 }2013 out.push_str(&hi_doc::source_to_ansi(&builder.build()));2014 }2015 for d in unspanned {2016 let prefix = match d.level {2017 DiagLevel::Error => "error",2018 DiagLevel::Warning => "warning",2019 };2020 writeln!(out, "{prefix}: {}", d.message).expect("fmt");2021 }2022 out2023 }2024 fn fmt_depth(d: u32) -> String {2025 if d == u32::MAX {2026 "none".into()2027 } else {2028 d.to_string()2029 }2030 }20312032 glob!("analysis_tests/*.jsonnet", |path| {2033 let code = fs::read_to_string(path).expect("read test file");2034 let src = Source::new_virtual("<test>".into(), code.clone().into());2035 let expr = crate::parse_jsonnet(&code, src.clone()).expect("parse");2036 let report = analyze_root(&expr, Vec::new());20372038 let diagnostics = render_diagnostics(src.code(), &report.diagnostics_list);2039 2040 let diagnostics = strip_ansi_escapes::strip_str(&diagnostics);2041 let rendered = format!(2042 "--- source ---\n{}\n--- root analysis ---\nobject_dependent_depth: {}\nlocal_dependent_depth: {}\nerrored: {}\n--- diagnostics ---\n{}--- lir ---\n{:#?}\n",2043 code.trim_end(),2044 fmt_depth(report.root_analysis.object_dependent_depth),2045 fmt_depth(report.root_analysis.local_dependent_depth),2046 report.errored,2047 diagnostics,2048 report.lir,2049 );2050 assert_snapshot!(rendered);2051 });2052 }2053}