@@ -1177,6 +1177,141 @@ let test_map_access_auto_deref_in_assignment () =
11771177 (* Without the fix this branch would fall through to the raw-pointer path *)
11781178 check bool " no raw pointer assignment" false (contains_substr output " count = map_ptr_0" )
11791179
1180+ (* * Tests for tail call fallback return fix.
1181+ These tests verify that every match arm containing a tail call emits an
1182+ explicit fallback return statement so the eBPF verifier can confirm that
1183+ all code paths terminate, even when bpf_tail_call() fails at runtime. *)
1184+
1185+ (* * Unit test: standalone IRTailCall also generates a fallback return and no
1186+ longer emits the legacy continue-execution comment. *)
1187+ let test_standalone_tail_call_fallback_xdp () =
1188+ let ctx = create_c_context () in
1189+ ctx.current_function_context_type < - Some " xdp" ;
1190+
1191+ let ctx_arg = make_ir_value (IRVariable " ctx" )
1192+ (IRPointer (IRStruct (" xdp_md" , [] ), make_bounds_info () )) test_pos in
1193+ let instr = make_ir_instruction
1194+ (IRTailCall (" tcp_handler" , [ctx_arg], 7 )) test_pos in
1195+ generate_c_instruction ctx instr;
1196+
1197+ let output = String. concat " \n " ctx.output_lines in
1198+ check bool " standalone bpf_tail_call emitted"
1199+ true (contains_substr output " bpf_tail_call(ctx, &prog_array, 7);" );
1200+ check bool " standalone XDP fallback return present"
1201+ true (contains_substr output " return XDP_PASS; /* tail call fallback */" );
1202+ check bool " standalone old continue-execution comment absent"
1203+ false (contains_substr output " If tail call fails, continue execution" );
1204+ ()
1205+
1206+ (* * Unit test: IRReturnTailCall in a constant match arm generates a fallback
1207+ return statement with XDP_PASS when the function context is "xdp". *)
1208+ let test_tail_call_fallback_constant_arm_xdp () =
1209+ let ctx = create_c_context () in
1210+ ctx.current_function_context_type < - Some " xdp" ;
1211+
1212+ let matched_val = make_ir_value (IRVariable " protocol" ) IRU32 test_pos in
1213+ let ctx_arg = make_ir_value (IRVariable " ctx" )
1214+ (IRPointer (IRStruct (" xdp_md" , [] ), make_bounds_info () )) test_pos in
1215+ let arms = [
1216+ { match_pattern = IRConstantPattern
1217+ (make_ir_value (IRLiteral (IntLit (Signed64 6L , None ))) IRU32 test_pos);
1218+ return_action = IRReturnTailCall (" tcp_handler" , [ctx_arg], 1 );
1219+ arm_pos = test_pos };
1220+ { match_pattern = IRDefaultPattern ;
1221+ return_action = IRReturnValue
1222+ (make_ir_value (IRLiteral (IntLit (Signed64 0L , None ))) IRU32 test_pos);
1223+ arm_pos = test_pos };
1224+ ] in
1225+ let instr = make_ir_instruction (IRMatchReturn (matched_val, arms)) test_pos in
1226+ generate_c_instruction ctx instr;
1227+
1228+ let output = String. concat " \n " ctx.output_lines in
1229+ check bool " bpf_tail_call emitted for constant arm"
1230+ true (contains_substr output " bpf_tail_call(ctx, &prog_array, 1)" );
1231+ check bool " XDP_PASS fallback return present"
1232+ true (contains_substr output " return XDP_PASS; /* tail call fallback */" );
1233+ check bool " old continue-execution comment absent"
1234+ false (contains_substr output " If tail call fails, continue execution" );
1235+ ()
1236+
1237+ (* * Unit test: IRReturnTailCall in a default match arm also generates a
1238+ fallback return, and TC context gives TC_ACT_OK. *)
1239+ let test_tail_call_fallback_default_arm_tc () =
1240+ let ctx = create_c_context () in
1241+ ctx.current_function_context_type < - Some " tc" ;
1242+
1243+ let matched_val = make_ir_value (IRVariable " proto" ) IRU32 test_pos in
1244+ let ctx_arg = make_ir_value (IRVariable " ctx" )
1245+ (IRPointer (IRStruct (" __sk_buff" , [] ), make_bounds_info () )) test_pos in
1246+ let arms = [
1247+ { match_pattern = IRConstantPattern
1248+ (make_ir_value (IRLiteral (IntLit (Signed64 6L , None ))) IRU32 test_pos);
1249+ return_action = IRReturnValue
1250+ (make_ir_value (IRLiteral (IntLit (Signed64 0L , None ))) IRU32 test_pos);
1251+ arm_pos = test_pos };
1252+ { match_pattern = IRDefaultPattern ;
1253+ return_action = IRReturnTailCall (" default_tc_handler" , [ctx_arg], 2 );
1254+ arm_pos = test_pos };
1255+ ] in
1256+ let instr = make_ir_instruction (IRMatchReturn (matched_val, arms)) test_pos in
1257+ generate_c_instruction ctx instr;
1258+
1259+ let output = String. concat " \n " ctx.output_lines in
1260+ check bool " bpf_tail_call emitted for default arm"
1261+ true (contains_substr output " bpf_tail_call(ctx, &prog_array, 2)" );
1262+ check bool " TC_ACT_OK fallback return present for TC context"
1263+ true (contains_substr output " return TC_ACT_OK; /* tail call fallback */" );
1264+ check bool " XDP_PASS fallback NOT present in TC context"
1265+ false (contains_substr output " return XDP_PASS;" );
1266+ check bool " old continue-execution comment absent"
1267+ false (contains_substr output " If tail call fails, continue execution" );
1268+ ()
1269+
1270+ (* * Unit test: IRReturnCall (implicit tail call with index 0) in both constant
1271+ and default arms generates fallback returns. Generic context (None) uses
1272+ "return 0" as the fallback. *)
1273+ let test_return_call_fallback_generic_context () =
1274+ let ctx = create_c_context () in
1275+ (* current_function_context_type left as None -> generic fallback "0" *)
1276+
1277+ let matched_val = make_ir_value (IRVariable " key" ) IRU32 test_pos in
1278+ let ctx_arg = make_ir_value (IRVariable " ctx" )
1279+ (IRPointer (IRStruct (" generic_ctx" , [] ), make_bounds_info () )) test_pos in
1280+ let arms = [
1281+ { match_pattern = IRConstantPattern
1282+ (make_ir_value (IRLiteral (IntLit (Signed64 1L , None ))) IRU32 test_pos);
1283+ return_action = IRReturnCall (" handler_one" , [ctx_arg]);
1284+ arm_pos = test_pos };
1285+ { match_pattern = IRDefaultPattern ;
1286+ return_action = IRReturnCall (" handler_default" , [ctx_arg]);
1287+ arm_pos = test_pos };
1288+ ] in
1289+ let instr = make_ir_instruction (IRMatchReturn (matched_val, arms)) test_pos in
1290+ generate_c_instruction ctx instr;
1291+
1292+ let output = String. concat " \n " ctx.output_lines in
1293+ (* Both arms use IRReturnCall which maps to index 0 *)
1294+ let bpf_calls = ref 0 in
1295+ let search_start = ref 0 in
1296+ (try while true do
1297+ let pos = Str. search_forward (Str. regexp_string " bpf_tail_call(ctx, &prog_array, 0)" ) output ! search_start in
1298+ incr bpf_calls;
1299+ search_start := pos + 1
1300+ done with Not_found -> () );
1301+ check bool " bpf_tail_call emitted in constant arm (IRReturnCall)"
1302+ true (! bpf_calls > = 1 );
1303+ check bool " bpf_tail_call emitted in default arm (IRReturnCall)"
1304+ true (! bpf_calls > = 2 );
1305+ check bool " generic fallback return 0 present"
1306+ true (contains_substr output " return 0; /* tail call fallback */" );
1307+ check bool " XDP_PASS fallback NOT present for generic context"
1308+ false (contains_substr output " return XDP_PASS;" );
1309+ check bool " TC_ACT_OK fallback NOT present for generic context"
1310+ false (contains_substr output " return TC_ACT_OK;" );
1311+ check bool " old continue-execution comment absent"
1312+ false (contains_substr output " If tail call fails, continue execution" );
1313+ ()
1314+
11801315(* * Test suite definition *)
11811316let suite =
11821317 [
@@ -1224,6 +1359,11 @@ let suite =
12241359 (" Global map redefinition fix" , `Quick , test_global_map_redefinition_fix);
12251360 (* Coverage for IRMapAccess auto-dereference path in generate_assignment *)
12261361 (" Map access auto-deref in assignment" , `Quick , test_map_access_auto_deref_in_assignment);
1362+ (* Tail call fallback return fix - verifier requires explicit return after bpf_tail_call() *)
1363+ (" Tail call fallback: standalone XDP context" , `Quick , test_standalone_tail_call_fallback_xdp);
1364+ (" Tail call fallback: constant arm XDP context" , `Quick , test_tail_call_fallback_constant_arm_xdp);
1365+ (" Tail call fallback: default arm TC context" , `Quick , test_tail_call_fallback_default_arm_tc);
1366+ (" Tail call fallback: IRReturnCall generic context" , `Quick , test_return_call_fallback_generic_context);
12271367 ]
12281368
12291369(* * Run all tests *)
0 commit comments