; E06-S06: interface lifetime and failure paths in x128. ; ; Runs under CR=$0E with a 4K heap at $4000. It checks: ; - delete through an interface value calls the class destructor once, frees ; the complete object, and clears the value so it cannot delete twice; ; - delete on a canonical null interface value is a no-op; ; - one object reachable through two interface tables is freed once, and the ; other value is refused afterwards; ; - an alias (a copy of the value) that outlives the deletion is a detectable ; failure: IF_ERR_FREED, no second destructor call, no heap change; ; - a null interface call faults predictably with IF_ERR_NULL, executes no ; target, and leaves dispatch working. ; ; MB_DATA flags 0..4 report the five phases. .setcpu "6502" .include "../../src/common/c128.inc" .include "../../src/common/mailbox.inc" .include "../../src/common/print.inc" .include "../../src/common/heap.inc" .include "../../src/common/class.inc" .include "../../src/common/frames.inc" .include "../../src/common/interfaces.inc" .import pr_char, pr_cr, pr_text .import heap_init, heap_free_total, heap_error .import frame_init .import obj_new, obj_init_ptr .import if_call, if_error .import if_delete HEAP_BASE = $4000 HEAP_PAGES = 16 .macro TEXT src, len lda #src ldy #len jsr pr_text .endmacro .macro PHASE n lda #n sta MB_CODE lda #$00 sta MB_CODE+1 .endmacro .macro MARK n lda #n sta MB_ADDR .endmacro .macro FAIL_CS bcc :+ jmp @fail : .endmacro .macro FAIL_CC bcs :+ jmp @fail : .endmacro .macro FAIL_NE beq :+ jmp @fail : .endmacro ; A word in A/X must equal the two bytes at addr. .macro CHK_WORD addr cmp addr bne @bad txa cmp addr+1 beq @ok @bad: jmp @fail @ok: .endmacro .segment "LOADADDR" .word $1C01 .segment "EXEHDR" .word basic_end .word 10 .byte $9E .byte "7181" .byte $00 basic_end: .word $0000 .segment "MAILBOX" .res MB_SIZE .segment "STARTUP" .export test_entry, test_end test_entry: php sei ldx #MB_SIZE - 1 lda #$00 @clr: sta MB_BASE,x dex bpl @clr lda #MB_MAGIC0_VALUE sta MB_MAGIC0 lda #MB_MAGIC1_VALUE sta MB_MAGIC1 lda #MB_PROD_PROGRAM sta MB_PRODUCER lda #MB_ST_RUNNING sta MB_STATUS lda #$00 sta MB_CODE sta MB_CODE+1 sta destroy_count sta hit_count ldx #$04 @flags: sta MB_DATA,x dex bpl @flags lda MMU_CR sta saved_cr lda #CR_RAM0_KERNAL sta MMU_CR lda #PR_CLEAR jsr pr_char TEXT title, title_len jsr pr_cr PHASE 1 lda #$00 ldx #>HEAP_BASE ldy #HEAP_PAGES jsr heap_init FAIL_CS MARK $01 jsr frame_init lda #$00 sta obj_init_ptr sta obj_init_ptr+1 ; 1: delete through an interface frees once and clears the value. MARK $02 lda #desc_life jsr obj_new FAIL_CS sta live_obj stx live_obj+1 jsr make_foo MARK $03 lda #val_foo jsr if_delete bcc :+ lda if_error sta MB_CODE lda #$08 sta MB_ADDR jmp @fail : MARK $04 lda if_error FAIL_NE lda destroy_count cmp #1 FAIL_NE MARK $05 lda destroy_self cmp live_obj FAIL_NE lda destroy_self+1 cmp live_obj+1 FAIL_NE lda heap_free_total cmp #$00 FAIL_NE lda heap_free_total+1 cmp #$10 FAIL_NE MARK $06 jsr chk_zero FAIL_CS MARK $07 lda #$01 sta MB_DATA+0 ; 2: delete on a canonical null value is a no-op. PHASE 2 MARK $11 ; BSS is not part of the PRG image, so state the null values here. ldx #$03 lda #$00 @init2: sta val_null,x sta val_objnull,x dex bpl @init2 lda #$C0 sta val_objnull+IF_TABLE lda #val_null jsr if_delete FAIL_CS MARK $12 lda if_error FAIL_NE lda destroy_count cmp #1 FAIL_NE ; A value with a table but no object is still null for deletion. lda #val_objnull jsr if_delete FAIL_CS lda destroy_count cmp #1 FAIL_NE lda #$01 sta MB_DATA+1 ; 3: one object, two interface tables: the first delete frees it, the ; second value is refused rather than reading a freed destructor. PHASE 3 lda #desc_life jsr obj_new FAIL_CS sta live_obj stx live_obj+1 jsr make_foo lda live_obj sta val_bar lda live_obj+1 sta val_bar+1 lda #tab_bar sta val_bar+IF_TABLE+1 lda #val_bar jsr if_delete FAIL_CS lda destroy_count cmp #2 FAIL_NE lda heap_free_total+1 cmp #$10 FAIL_NE lda #val_foo jsr if_delete FAIL_CC lda if_error cmp #IF_ERR_FREED FAIL_NE lda destroy_count cmp #2 FAIL_NE lda heap_free_total+1 cmp #$10 FAIL_NE lda #$01 sta MB_DATA+2 ; 4: an alias copy that outlives the deletion is detected. PHASE 4 lda #desc_life jsr obj_new FAIL_CS sta live_obj stx live_obj+1 jsr make_foo ldx #$00 @alias: lda val_foo,x sta val_alias,x inx cpx #IF_VALUE_SIZE bne @alias lda #val_foo jsr if_delete FAIL_CS lda destroy_count cmp #3 FAIL_NE lda #val_alias jsr if_delete FAIL_CC lda if_error cmp #IF_ERR_FREED FAIL_NE lda destroy_count cmp #3 FAIL_NE lda heap_free_total cmp #$00 FAIL_NE lda heap_free_total+1 cmp #$10 FAIL_NE lda #$01 sta MB_DATA+3 ; 5: a null call faults before any target runs, and dispatch recovers. PHASE 5 lda #val_null ldy #$00 jsr if_call FAIL_CC lda if_error cmp #IF_ERR_NULL FAIL_NE lda hit_count FAIL_NE lda #desc_life jsr obj_new FAIL_CS sta live_obj stx live_obj+1 jsr make_foo lda #val_foo ldy #$00 jsr if_call FAIL_NE lda if_error FAIL_NE lda hit_count cmp #1 FAIL_NE lda #val_foo jsr if_delete FAIL_CS lda destroy_count cmp #4 FAIL_NE lda #$01 sta MB_DATA+4 TEXT ok_msg, ok_len jsr pr_cr lda #MB_ST_PASS sta MB_STATUS lda #$00 sta MB_CODE sta MB_CODE+1 jmp @restore @fail: lda #MB_ST_FAIL sta MB_STATUS TEXT fail_msg, fail_len jsr pr_cr @restore: lda saved_cr sta MMU_CR plp rts ; val_foo = live_obj + tab_foo. make_foo: lda live_obj sta val_foo lda live_obj+1 sta val_foo+1 lda #tab_foo sta val_foo+IF_TABLE+1 rts ; C=1 unless the four bytes at val_foo are zero. chk_zero: ldx #$00 @z: lda val_foo,x bne @nz inx cpx #IF_VALUE_SIZE bne @z clc rts @nz: sec rts .segment "CODE" ; Destructor: records how often and for which object it ran. destroy_life: sta destroy_self stx destroy_self+1 inc destroy_count rts ; Slot 0 target used by the null-call check. m_hit: inc hit_count lda #$11 ldx #$00 rts .segment "DATA" desc_life: .word 4 ; header + one word field .word destroy_life .byte 0 .word 0 .byte 0 .assert * - desc_life = CLASS_DESC_SIZE, error, "life descriptor size" tab_foo: .word m_hit tab_bar: .word m_hit .assert tab_bar - tab_foo = IF_SLOT_SIZE, error, "one slot per table" .segment "RODATA" title: .byte "NOVA128 LIFETIME TEST" title_len = * - title ok_msg: .byte "LIFETIME OK" ok_len = * - ok_msg fail_msg: .byte "LIFETIME FAIL" fail_len = * - fail_msg .segment "BSS" saved_cr: .res 1 live_obj: .res 2 destroy_count: .res 1 destroy_self: .res 2 hit_count: .res 1 val_foo: .res IF_VALUE_SIZE val_bar: .res IF_VALUE_SIZE val_alias: .res IF_VALUE_SIZE val_null: .res IF_VALUE_SIZE val_objnull: .res IF_VALUE_SIZE test_end: