@@ -900,6 +900,17 @@ impl<T: Copy + Into<usize> + PartialEq> UnionFind<T> {
900900 }
901901}
902902
903+ #[ derive( Debug , PartialEq ) ]
904+ pub enum ValidationError {
905+ // All validation errors come with the function's representation as the first argument.
906+ BlockHasNoTerminator ( String , BlockId ) ,
907+ // The terminator and its actual position
908+ TerminatorNotAtEnd ( String , BlockId , InsnId , usize ) ,
909+ /// Expected length, actual length
910+ MismatchedBlockArity ( String , BlockId , usize , usize ) ,
911+ }
912+
913+
903914/// A [`Function`], which is analogous to a Ruby ISeq, is a control-flow graph of [`Block`]s
904915/// containing instructions.
905916#[ derive( Debug ) ]
@@ -2005,6 +2016,51 @@ impl Function {
20052016 None => { } ,
20062017 }
20072018 }
2019+
2020+
2021+ /// Validates the following:
2022+ /// 1. Basic block jump args match parameter arity.
2023+ /// 2. Every terminator must be in the last position.
2024+ /// 3. Every block must have a terminator.
2025+ fn validate_block_terminators_and_jumps ( & self ) -> Result < ( ) , ValidationError > {
2026+ for block_id in self . rpo ( ) {
2027+ let mut block_has_terminator = false ;
2028+ let insns = & self . blocks [ block_id. 0 ] . insns ;
2029+ for ( idx, insn_id) in insns. iter ( ) . enumerate ( ) {
2030+ let insn = self . find ( * insn_id) ;
2031+ match & insn {
2032+ Insn :: Jump ( BranchEdge { target, args} )
2033+ | Insn :: IfTrue { val : _, target : BranchEdge { target, args} }
2034+ | Insn :: IfFalse { val : _, target : BranchEdge { target, args} } => {
2035+ let target_block = & self . blocks [ target. 0 ] ;
2036+ let target_len = target_block. params . len ( ) ;
2037+ let args_len = args. len ( ) ;
2038+ if target_len != args_len {
2039+ return Err ( ValidationError :: MismatchedBlockArity ( format ! ( "{:?}" , self ) , block_id, target_len, args_len) )
2040+ }
2041+ }
2042+ _ => { }
2043+ }
2044+ if !insn. is_terminator ( ) {
2045+ continue ;
2046+ }
2047+ block_has_terminator = true ;
2048+ if idx != insns. len ( ) - 1 {
2049+ return Err ( ValidationError :: TerminatorNotAtEnd ( format ! ( "{:?}" , self ) , block_id, * insn_id, idx) ) ;
2050+ }
2051+ }
2052+ if !block_has_terminator {
2053+ return Err ( ValidationError :: BlockHasNoTerminator ( format ! ( "{:?}" , self ) , block_id) ) ;
2054+ }
2055+ }
2056+ Ok ( ( ) )
2057+ }
2058+
2059+ /// Run all validation passes we have.
2060+ pub fn validate ( & self ) -> Result < ( ) , ValidationError > {
2061+ self . validate_block_terminators_and_jumps ( ) ?;
2062+ Ok ( ( ) )
2063+ }
20082064}
20092065
20102066impl < ' a > std:: fmt:: Display for FunctionPrinter < ' a > {
@@ -2241,6 +2297,7 @@ pub enum ParseError {
22412297 StackUnderflow ( FrameState ) ,
22422298 UnknownParameterType ( ParameterType ) ,
22432299 MalformedIseq ( u32 ) , // insn_idx into iseq_encoded
2300+ Validation ( ValidationError ) ,
22442301}
22452302
22462303/// Return the number of locals in the current ISEQ (includes parameters)
@@ -2966,6 +3023,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
29663023 }
29673024
29683025 fun. profiles = Some ( profiles) ;
3026+ if let Err ( e) = fun. validate ( ) {
3027+ return Err ( ParseError :: Validation ( e) ) ;
3028+ }
29693029 Ok ( fun)
29703030}
29713031
@@ -3069,6 +3129,72 @@ mod rpo_tests {
30693129 }
30703130}
30713131
3132+ #[ cfg( test) ]
3133+ mod validation_tests {
3134+ use super :: * ;
3135+
3136+ #[ track_caller]
3137+ fn assert_matches_err ( res : Result < ( ) , ValidationError > , expected : ValidationError ) {
3138+ match res {
3139+ Err ( validation_err) => {
3140+ assert_eq ! ( validation_err, expected) ;
3141+ }
3142+ Ok ( _) => assert ! ( false , "Expected validation error" ) ,
3143+ }
3144+ }
3145+
3146+ #[ test]
3147+ fn one_block_no_terminator ( ) {
3148+ let mut function = Function :: new ( std:: ptr:: null ( ) ) ;
3149+ let entry = function. entry_block ;
3150+ let val = function. push_insn ( entry, Insn :: Const { val : Const :: Value ( Qnil ) } ) ;
3151+ assert_matches_err ( function. validate ( ) , ValidationError :: BlockHasNoTerminator ( format ! ( "{:?}" , function) , entry) ) ;
3152+ }
3153+
3154+ #[ test]
3155+ fn one_block_terminator_not_at_end ( ) {
3156+ let mut function = Function :: new ( std:: ptr:: null ( ) ) ;
3157+ let entry = function. entry_block ;
3158+ let val = function. push_insn ( entry, Insn :: Const { val : Const :: Value ( Qnil ) } ) ;
3159+ let insn_id = function. push_insn ( entry, Insn :: Return { val } ) ;
3160+ function. push_insn ( entry, Insn :: Const { val : Const :: Value ( Qnil ) } ) ;
3161+ assert_matches_err ( function. validate ( ) , ValidationError :: TerminatorNotAtEnd ( format ! ( "{:?}" , function) , entry, insn_id, 1 ) ) ;
3162+ }
3163+
3164+ #[ test]
3165+ fn iftrue_mismatch_args ( ) {
3166+ let mut function = Function :: new ( std:: ptr:: null ( ) ) ;
3167+ let entry = function. entry_block ;
3168+ let side = function. new_block ( ) ;
3169+ let exit = function. new_block ( ) ;
3170+ let val = function. push_insn ( entry, Insn :: Const { val : Const :: Value ( Qnil ) } ) ;
3171+ function. push_insn ( entry, Insn :: IfTrue { val, target : BranchEdge { target : side, args : vec ! [ val, val, val] } } ) ;
3172+ assert_matches_err ( function. validate ( ) , ValidationError :: MismatchedBlockArity ( format ! ( "{:?}" , function) , entry, 0 , 3 ) ) ;
3173+ }
3174+
3175+ #[ test]
3176+ fn iffalse_mismatch_args ( ) {
3177+ let mut function = Function :: new ( std:: ptr:: null ( ) ) ;
3178+ let entry = function. entry_block ;
3179+ let side = function. new_block ( ) ;
3180+ let exit = function. new_block ( ) ;
3181+ let val = function. push_insn ( entry, Insn :: Const { val : Const :: Value ( Qnil ) } ) ;
3182+ function. push_insn ( entry, Insn :: IfFalse { val, target : BranchEdge { target : side, args : vec ! [ val, val, val] } } ) ;
3183+ assert_matches_err ( function. validate ( ) , ValidationError :: MismatchedBlockArity ( format ! ( "{:?}" , function) , entry, 0 , 3 ) ) ;
3184+ }
3185+
3186+ #[ test]
3187+ fn jump_mismatch_args ( ) {
3188+ let mut function = Function :: new ( std:: ptr:: null ( ) ) ;
3189+ let entry = function. entry_block ;
3190+ let side = function. new_block ( ) ;
3191+ let exit = function. new_block ( ) ;
3192+ let val = function. push_insn ( entry, Insn :: Const { val : Const :: Value ( Qnil ) } ) ;
3193+ function. push_insn ( entry, Insn :: Jump ( BranchEdge { target : side, args : vec ! [ val, val, val] } ) ) ;
3194+ assert_matches_err ( function. validate ( ) , ValidationError :: MismatchedBlockArity ( format ! ( "{:?}" , function) , entry, 0 , 3 ) ) ;
3195+ }
3196+ }
3197+
30723198#[ cfg( test) ]
30733199mod infer_tests {
30743200 use super :: * ;
@@ -4701,6 +4827,7 @@ mod opt_tests {
47014827 unsafe { crate :: cruby:: rb_zjit_profile_disable ( iseq) } ;
47024828 let mut function = iseq_to_hir ( iseq) . unwrap ( ) ;
47034829 function. optimize ( ) ;
4830+ function. validate ( ) . unwrap ( ) ;
47044831 assert_function_hir ( function, hir) ;
47054832 }
47064833
0 commit comments