@@ -277,6 +277,9 @@ pub struct JSShape {
277277 pub deleted_prop_count : i32 ,
278278 pub prop : * mut JSShapeProperty ,
279279 pub prop_hash : * mut u32 ,
280+ // Linked list of objects which currently use this shape. Each object's
281+ // `next_in_shape` pointer links to the next object in the list.
282+ pub first_object : * mut JSObject ,
280283 pub proto : * mut JSObject ,
281284}
282285
@@ -394,6 +397,8 @@ pub struct JSObject {
394397 pub shape : * mut JSShape ,
395398 pub prop : * mut JSProperty ,
396399 pub first_weak_ref : * mut JSObject ,
400+ // Pointer to next object in the shape's object list
401+ pub next_in_shape : * mut JSObject ,
397402}
398403
399404#[ repr( C ) ]
@@ -471,9 +476,47 @@ impl JSRuntime {
471476
472477 if ( * sh) . prop_count >= ( * sh) . prop_size {
473478 let new_size = if ( * sh) . prop_size == 0 { 4 } else { ( * sh) . prop_size * 3 / 2 } ;
479+ // Resize the shape's property descriptor array
474480 if self . resize_shape ( sh, new_size) < 0 {
475481 return ( -1 , prev_prop_size) ;
476482 }
483+
484+ // For all objects that currently use this shape, reallocate their
485+ // JSProperty arrays to match the new shape size so every object
486+ // has space for the newly added property slots.
487+ let mut obj_ptr = ( * sh) . first_object ;
488+ while !obj_ptr. is_null ( ) {
489+ // keep next pointer in case reallocation moves memory
490+ let next_obj = ( * obj_ptr) . next_in_shape ;
491+
492+ let old_prop = ( * obj_ptr) . prop ;
493+ let new_prop_obj = self . js_realloc_rt ( old_prop as * mut c_void , ( new_size as usize ) * std:: mem:: size_of :: < JSProperty > ( ) )
494+ as * mut JSProperty ;
495+
496+ if new_prop_obj. is_null ( ) {
497+ return ( -1 , prev_prop_size) ;
498+ }
499+
500+ // If we just allocated the array, zero-initialize all slots.
501+ if old_prop. is_null ( ) {
502+ // Initialize all slots to JS_UNDEFINED (correct default), avoid raw zeros
503+ for i in 0 ..( new_size as isize ) {
504+ ( * new_prop_obj. offset ( i) ) . u . value = JS_UNDEFINED ;
505+ }
506+ } else if new_size > prev_prop_size {
507+ // Zero only the newly-added slots
508+ let start_index = prev_prop_size as usize ;
509+ let new_slots = ( new_size - prev_prop_size) as usize ;
510+ if new_slots > 0 {
511+ for i in start_index..( start_index + new_slots) {
512+ ( * new_prop_obj. add ( i) ) . u . value = JS_UNDEFINED ;
513+ }
514+ }
515+ }
516+
517+ ( * obj_ptr) . prop = new_prop_obj;
518+ obj_ptr = next_obj;
519+ }
477520 }
478521
479522 // Enable hash if needed
@@ -606,6 +649,7 @@ impl JSRuntime {
606649 ( * sh) . deleted_prop_count = 0 ;
607650 ( * sh) . prop = std:: ptr:: null_mut ( ) ;
608651 ( * sh) . prop_hash = std:: ptr:: null_mut ( ) ;
652+ ( * sh) . first_object = std:: ptr:: null_mut ( ) ;
609653 ( * sh) . proto = proto;
610654 sh
611655 }
@@ -642,60 +686,30 @@ pub unsafe fn JS_DefinePropertyValue(ctx: *mut JSContext, this_obj: JSValue, pro
642686 // Note: In real QuickJS, we might need to clone shape if it is shared
643687 // For now, assume shape is unique to object or we modify it in place (dangerous if shared)
644688
645- let ( idx, prev_prop_size ) = unsafe { ( * ( * ctx) . rt ) . add_property ( sh, prop, flags as u8 ) } ;
689+ let ( idx, _prev_prop_size ) = unsafe { ( * ( * ctx) . rt ) . add_property ( sh, prop, flags as u8 ) } ;
646690 if idx < 0 {
647691 return -1 ;
648692 }
649693
650- // Resize object prop array if needed
651- // JSObject prop array stores JSProperty (values)
652- // JSShape prop array stores JSShapeProperty (names/flags)
653- // They must match in size/index
654-
655- // TODO: Resize object prop array
656- // For now, let's assume we have enough space or implement resize logic for object prop
657-
658- // Actually, we need to implement object prop resizing here
659- // But JSObject definition: pub prop: *mut JSProperty
660- // We don't store prop_size in JSObject?
661- // QuickJS stores it in JSShape? No.
662- // QuickJS: JSObject has no size field. It relies on Shape?
663- // Ah, JSObject allocates prop array based on shape->prop_size?
664- // Or maybe it reallocates when shape grows?
665-
666- // Let's look at QuickJS:
667- // JS_DefinePropertyValue -> JS_DefineProperty -> add_property
668- // add_property modifies shape.
669- // If shape grows, we need to grow object's prop array too?
670- // Yes, but how do we know the current size of object's prop array?
671- // It seems we assume it matches shape's prop_count or prop_size?
672-
673- // Let's implement a simple resize for object prop
694+ // After add_property we should have ensured the shape has enough capacity and
695+ // that objects using this shape have their prop arrays resized. However it's
696+ // still possible this particular object has no prop array (for example a
697+ // newly created object that was not present when the shape grew). In that
698+ // case allocate a prop array sized to the current shape.
674699 let old_prop = unsafe { ( * p) . prop } ;
675- let new_prop = unsafe {
676- ( * ( * ctx) . rt ) . js_realloc_rt (
677- ( * p) . prop as * mut c_void ,
678- ( ( * sh) . prop_size as usize ) * std:: mem:: size_of :: < JSProperty > ( ) ,
679- ) as * mut JSProperty
680- } ;
681-
682- if new_prop. is_null ( ) {
683- return -1 ;
684- }
685- unsafe { ( * p) . prop = new_prop } ;
686- // If the prop array was just created, zero-initialize it to avoid reading
687- // uninitialized JSProperty values later.
688- if old_prop. is_null ( ) && !new_prop. is_null ( ) {
689- let size_bytes = unsafe { ( ( * sh) . prop_size as usize ) * std:: mem:: size_of :: < JSProperty > ( ) } ;
690- unsafe { std:: ptr:: write_bytes ( new_prop as * mut u8 , 0 , size_bytes) } ;
691- } else if !old_prop. is_null ( ) && unsafe { ( * sh) . prop_size } > prev_prop_size {
692- // Zero only the newly-added slots so we don't overwrite existing properties.
693- let start_index = prev_prop_size as usize ;
694- let new_slots = ( unsafe { ( * sh) . prop_size } - prev_prop_size) as usize ;
695- if new_slots > 0 {
696- let start_ptr = unsafe { new_prop. add ( start_index) } as * mut u8 ;
697- let bytes = new_slots * std:: mem:: size_of :: < JSProperty > ( ) ;
698- unsafe { std:: ptr:: write_bytes ( start_ptr, 0 , bytes) } ;
700+ if old_prop. is_null ( ) && unsafe { ( * sh) . prop_size } > 0 {
701+ let size = unsafe { ( * sh) . prop_size as usize } * std:: mem:: size_of :: < JSProperty > ( ) ;
702+ let new_prop = unsafe { ( * ( * ctx) . rt ) . js_realloc_rt ( std:: ptr:: null_mut ( ) , size) as * mut JSProperty } ;
703+ if new_prop. is_null ( ) {
704+ return -1 ;
705+ }
706+ unsafe {
707+ ( * p) . prop = new_prop;
708+ // initialize slots to JS_UNDEFINED
709+ let n = ( * sh) . prop_size as isize ;
710+ for i in 0 ..n {
711+ ( * new_prop. offset ( i) ) . u . value = JS_UNDEFINED ;
712+ }
699713 }
700714 }
701715
@@ -877,6 +891,17 @@ pub unsafe fn JS_NewObject(ctx: *mut JSContext) -> JSValue {
877891 }
878892 ( * obj) . prop = std:: ptr:: null_mut ( ) ;
879893 ( * obj) . first_weak_ref = std:: ptr:: null_mut ( ) ;
894+ ( * obj) . next_in_shape = std:: ptr:: null_mut ( ) ;
895+
896+ // Link object into its shape's object list so that when the shape changes
897+ // (for example when its prop_size grows) we can update all objects using
898+ // the same shape.
899+ if !( * obj) . shape . is_null ( ) {
900+ let sh = ( * obj) . shape ;
901+ // prepend to shape's list
902+ ( * obj) . next_in_shape = ( * sh) . first_object ;
903+ ( * sh) . first_object = obj;
904+ }
880905 JSValue :: new_ptr ( JS_TAG_OBJECT , obj as * mut c_void )
881906 }
882907}
@@ -6967,9 +6992,29 @@ unsafe fn js_free_object(rt: *mut JSRuntime, v: JSValue) {
69676992 ( * rt) . js_free_rt ( ( * p) . prop as * mut c_void ) ;
69686993 ( * p) . prop = std:: ptr:: null_mut ( ) ;
69696994 }
6970- // Free shape
6995+ // Unlink from shape's object list and free shape only if no objects remain
69716996 if !( * p) . shape . is_null ( ) {
6972- ( * rt) . js_free_shape ( ( * p) . shape ) ;
6997+ let sh = ( * p) . shape ;
6998+ // unlink p from sh->first_object list
6999+ if ( * sh) . first_object == p {
7000+ ( * sh) . first_object = ( * p) . next_in_shape ;
7001+ } else {
7002+ let mut prev = ( * sh) . first_object ;
7003+ while !prev. is_null ( ) {
7004+ if ( * prev) . next_in_shape == p {
7005+ ( * prev) . next_in_shape = ( * p) . next_in_shape ;
7006+ break ;
7007+ }
7008+ prev = ( * prev) . next_in_shape ;
7009+ }
7010+ }
7011+ // clear link for p
7012+ ( * p) . next_in_shape = std:: ptr:: null_mut ( ) ;
7013+
7014+ // if no objects remain using this shape, free it
7015+ if ( * sh) . first_object . is_null ( ) {
7016+ ( * rt) . js_free_shape ( sh) ;
7017+ }
69737018 ( * p) . shape = std:: ptr:: null_mut ( ) ;
69747019 }
69757020 // Free object struct
0 commit comments