3939#include "zjit.h"
4040#include "ruby/st.h"
4141#include "ruby/vm.h"
42+ #include "ruby/debug.h"
4243#include "vm_core.h"
4344#include "vm_callinfo.h"
4445#include "vm_debug.h"
@@ -1073,6 +1074,33 @@ vm_block_handler_escape(const rb_execution_context_t *ec, VALUE block_handler)
10731074 return Qnil ;
10741075}
10751076
1077+ /*
1078+ * Simulate rb_profile_frames arriving via signal at an arbitrary point
1079+ * during env escape, to reproduce bugs where a profiler interrupts
1080+ * vm_make_env_each mid-mutation.
1081+ *
1082+ * Gated behind RUBY_DEBUG_PROFILE_DURING_ESCAPE=1 so that miniruby
1083+ * and normal builds are unaffected. Run your reproducer with:
1084+ *
1085+ * RUBY_DEBUG_PROFILE_DURING_ESCAPE=1 ./miniruby bug_example.rb
1086+ */
1087+ // Simulate rb_profile_frames arriving via signal at an arbitrary point
1088+ // during env escape, to reproduce bugs where a profiler interrupts
1089+ // vm_make_env_each mid-mutation.
1090+ static void
1091+ vm_escape_debug_profile_frames (void )
1092+ {
1093+ static int enabled = -1 ;
1094+ if (UNLIKELY (enabled == -1 )) {
1095+ enabled = (getenv ("RUBY_DEBUG_PROFILE_DURING_ESCAPE" ) != NULL );
1096+ }
1097+ if (LIKELY (!enabled )) return ;
1098+
1099+ VALUE debug_buff [128 ];
1100+ int debug_lines [128 ];
1101+ rb_profile_frames (0 , 128 , debug_buff , debug_lines );
1102+ }
1103+
10761104static VALUE
10771105vm_make_env_each (const rb_execution_context_t * const ec , rb_control_frame_t * const cfp )
10781106{
@@ -1084,6 +1112,8 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co
10841112 return VM_ENV_ENVVAL (ep );
10851113 }
10861114
1115+ vm_escape_debug_profile_frames ();
1116+
10871117 if (!VM_ENV_LOCAL_P (ep )) {
10881118 const VALUE * prev_ep = VM_ENV_PREV_EP (ep );
10891119 if (!VM_ENV_ESCAPED_P (prev_ep )) {
@@ -1095,16 +1125,21 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co
10951125 }
10961126
10971127 vm_make_env_each (ec , prev_cfp );
1128+ vm_escape_debug_profile_frames ();
10981129 VM_FORCE_WRITE_SPECIAL_CONST (& ep [VM_ENV_DATA_INDEX_SPECVAL ], VM_GUARDED_PREV_EP (prev_cfp -> ep ));
1130+ vm_escape_debug_profile_frames ();
10991131 }
11001132 }
11011133 else {
11021134 VM_ASSERT (VM_ENV_LOCAL_P (ep ));
11031135 VALUE block_handler = VM_ENV_BLOCK_HANDLER (ep );
11041136
11051137 if (block_handler != VM_BLOCK_HANDLER_NONE ) {
1138+ vm_escape_debug_profile_frames ();
11061139 VALUE blockprocval = vm_block_handler_escape (ec , block_handler );
1140+ vm_escape_debug_profile_frames ();
11071141 VM_STACK_ENV_WRITE (ep , VM_ENV_DATA_INDEX_SPECVAL , blockprocval );
1142+ vm_escape_debug_profile_frames ();
11081143 }
11091144 }
11101145
@@ -1147,7 +1182,9 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co
11471182
11481183 // Careful with order in the following sequence. Each allocation can move objects.
11491184 env_body = ALLOC_N (VALUE , env_size );
1185+ vm_escape_debug_profile_frames ();
11501186 rb_env_t * env = IMEMO_NEW (rb_env_t , imemo_env , 0 );
1187+ vm_escape_debug_profile_frames ();
11511188
11521189 // Set up env without WB since it's brand new (similar to newobj_init(), newobj_fill())
11531190 MEMCPY (env_body , ep - (local_size - 1 /* specval */ ), VALUE , local_size );
@@ -1159,10 +1196,16 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co
11591196 env -> ep = env_ep ;
11601197 env -> env = env_body ;
11611198 env -> env_size = env_size ;
1199+ vm_escape_debug_profile_frames ();
11621200
11631201 cfp -> ep = env_ep ;
1202+ vm_escape_debug_profile_frames ();
1203+
11641204 VM_ENV_FLAGS_SET (env_ep , VM_ENV_FLAG_ESCAPED | VM_ENV_FLAG_WB_REQUIRED );
1205+ vm_escape_debug_profile_frames ();
1206+
11651207 VM_STACK_ENV_WRITE (ep , 0 , (VALUE )env ); /* GC mark */
1208+ vm_escape_debug_profile_frames ();
11661209
11671210#if 0
11681211 for (i = 0 ; i < local_size ; i ++ ) {
0 commit comments