Page 3 of 3
Re: FreeStructure() crash
Posted: Wed Oct 23, 2024 2:43 pm
by Joubarbe
1/ Same result.
2/ I use With everywhere

3/ I draw my app on a Windows canvas and I'm under the impression that putting a delay in the loop (even if it's a WaitWindowEvent loop) that there is less crash... Really a wild guess, but I think it's something with events overlapping / interfering.
Re: FreeStructure() crash
Posted: Wed Oct 23, 2024 4:36 pm
by Joubarbe
Could someone try the following:
https://drive.google.com/file/d/1sr8Qoo ... sp=sharing
Launch Test.exe, hit Tab once to highlight the "Test1" window, then spam through F1, F2 and F3 until it crashes. This is not the same crash, but it might be related. If it
doesn't crash on your PC, then I'll know something is wrong with my system.
EDIT: I've just done an interesting debug: print all the memory addresses of all my views, then print them again just before it crashes. Result: it's the same addresses.
Code: Select all
Procedure ClearControls(*view._View) : With *view
If ListSize(\labels()) > 0 : ClearList(\labels()) : EndIf ; Crashes here, read error at random addresses, but always a low value (ie. not starting from *view address)
; .......
Re: FreeStructure() crash
Posted: Wed Oct 23, 2024 6:07 pm
by Fred
It's not advised to run random .exe. To pinpoint these kind of bug, you can try to remove code small part by small part until it doesn't crash and then you add the some.of the last part and so on. I once spent 3 days on a crazy pointer in the IDE but it wasn't really any other way. That's the problem of native programs, debugging can be awful.
Re: FreeStructure() crash
Posted: Wed Oct 23, 2024 6:10 pm
by spikey
Joubarbe wrote: Wed Oct 23, 2024 4:36 pm
EDIT: I've just done an interesting debug: print all the memory addresses of all my views, then print them again just before it crashes. Result: it's the same addresses.
Yup, that's feasible. Consider this code:
Code: Select all
Structure THING
AnInt.i
ALong.l
AByte.b
EndStructure
Define x.i
NewList Things.THING()
AddElement(Things())
Things()\AnInt = 1
Things()\ALong = 1
Things()\AByte = 1
AddElement(Things())
Things()\AnInt = #MAXDWORD
Things()\ALong = #MAXDWORD
Things()\AByte = #MAXBYTE
x = ListSize(Things())
ForEach Things()
Debug @Things()
Next Things()
ClearList(Things())
The (shortened) assembly for this looks like:
Code: Select all
; ...
;
; Structure THING
; AnInt.i
; ALong.l
; AByte.b
; EndStructure
;
; Define x.i
;
; NewList Things.THING()
MOV r9,7
XOR r8,r8
LEA rdx,[t_Things]
MOV rcx,13
CALL PB_NewList
;
; AddElement(Things())
PUSH qword [t_Things]
POP rcx
CALL PB_AddElement
; Things()\AnInt = 1
MOV rbp,qword [t_Things+8]
MOV qword [rbp+16],1
; Things()\ALong = 1
MOV dword [rbp+24],1
; Things()\AByte = 1
MOV byte [rbp+28],1
;
; AddElement(Things())
PUSH qword [t_Things]
POP rcx
CALL PB_AddElement
; Things()\AnInt = #MAXDWORD
MOV rbp,qword [t_Things+8]
MOV rax,4294967295
MOV qword [rbp+16],rax
; Things()\ALong = #MAXDWORD
MOV rax,4294967295
MOV dword [rbp+24],eax
; Things()\AByte = #MAXBYTE
MOV byte [rbp+28],255
;
; x = ListSize(Things())
PUSH qword [t_Things]
POP rcx
CALL PB_ListSize
MOV qword [v_x],rax
;
; ForEach Things()
MOV rcx,qword [t_Things]
CALL PB_ResetList
_ForEach1:
MOV rcx,qword [t_Things]
CALL PB_NextElement
OR rax,rax
JZ _Next1
; Debug @Things()
; Next Things()
JMP _ForEach1
_Next1:
;
; ClearList(Things())
PUSH qword [t_Things]
POP rcx
CALL PB_ClearList
;
; ...
;
section '.bss' readable writeable
_PB_BSSSection:
pb_bssalign 8
;
I_BSSStart:
_PB_MemoryBase:
PB_MemoryBase: rq 1
_PB_Instance:
PB_Instance: rq 1
PB_ExitCode: rq 1
;
pb_bssalign 8
PB_DataPointer rq 1
v_x rq 1
pb_bssalign 8
pb_bssalign 8
pb_bssalign 8
pb_bssalign 8
t_Things rq 2
I_BSSEnd:
section '.data' data readable writeable
SYS_EndDataSection:
In each list library function call the base address of the list is passed to the function via RCX. The memory address of the base of the list is not passed directly to the function. If something manages to alter the value of RCX between the setup and the function call the function will get a corrupt parameter.
For example if I were to insert an INC RCX at line 66 in the assembly after the push/pop, PB_ClearList would get a corrupt pointer to the list. (OK I know this isn't what's actually happening to you, I'm just trying to illustrate a problem.)
This is a primitive example, I've not used a procedure call. Where procedure calls are used there is a second potential problem area because the stack will get involved too so it's possible that the stack data could have been incorrectly modified by another actor.
The underlying list data structure could remain intact in both of these cases.
Re: FreeStructure() crash
Posted: Wed Oct 23, 2024 6:19 pm
by Joubarbe
Okay thank you Fred and spikey (and all who helped). I'm going to try to fix that myself, because at this point I'm pretty sure that it's coming from me, and not my system or PB. It just has consequences that I don't understand, like a crash on ClearList(). But yeah, that's the fun of programming

Re: FreeStructure() crash
Posted: Wed Oct 23, 2024 8:01 pm
by SMaag
Code: Select all
Structure _View
List *controls._Control()
EndStructure
What happens if you use only
Code: Select all
Structure _View
List controls.i
EndStructure
;OR
Structure _View
List *controls
EndStructure
Or other idea for testing what's the problem
do not put the List into a structure.
use
Code: Select all
; Define my_view._View ; don't to this
NewList *my_view._Control() ; do this
Maybe my_view\controls() sometimes isn't a Pointer to _control Structure. It is a value or a Pointer to anything else (maybe *_Button or *_Label) because anywhere you did not take care of it. And because of PB is not TypeSafe you can do this without any warning!
I use in my software for such things a pointer to itself as first Element
Code: Select all
Structure _Control
ptrMe.i ; ptrMe must contain the Pointer to the structure itself (@_Control)
EndStructure
With this trick I can always validate the Pointers to my hirarchical structures!
Yes it's a little more work but it helps!
For me it's always a problem that PB hides the '*' so a pointer and a value looks identical.
I use ptr as prefix to prevent such problems.
I would use
Code: Select all
Structure _View
List *ptrControls._Control()
EndStructure
Re: FreeStructure() crash
Posted: Thu Oct 24, 2024 5:44 am
by Joubarbe
Code: Select all
Structure _Control
ptrMe.i ; ptrMe must contain the Pointer to the structure itself (@_Control)
EndStructure
With this trick I can always validate the Pointers to my hirarchical structures!
Could you elaborate on this? I actually didn't know that you could assign a structure to a pointer. I'm not sure I fully understand the use of it though.
For me it's always a problem that PB hides the '*' so a pointer and a value looks identical.
Very true
Thanks for your input.
Re: FreeStructure() crash
Posted: Thu Oct 24, 2024 5:08 pm
by SMaag
I had some tricky Pointer Erros with Structures, because of that I added my own Structure checks with a Pointer on itself and a StructureID.
Now I put such things in a seperate Module.
Here is more or less my version adapted to your Structure problem
Code: Select all
EnableExplicit
DeclareModule Ctrls
Enumeration EStructureID
#Struct_unknown
#Struct_Control_Base = $10
#Struct_Control_Button
#Struct_Control_Label
EndEnumeration
Structure _ControlBase
ptrMe.i ; helper variable to hold the Pointer to itself. It's only for user defined Pointer Checks
StructID.i ; for more detailed Structure Checks
EndStructure
Structure _Button Extends _ControlBase
EndStructure
Structure _Label Extends _ControlBase
EndStructure
Declare.i Allocate_Button()
Declare.i Allocate_Label()
Declare.i Free_Control(*Ctrl._ControlBase)
EndDeclareModule
Module Ctrls
Procedure.i Allocate_Button()
Protected *ctrl._ControlBase
*ctrl = AllocateStructure(_Button)
*ctrl\ptrMe = *ctrl
*ctrl\StructID = #Struct_Control_Button
ProcedureReturn *ctrl
EndProcedure
Procedure.i Allocate_Label()
Protected *ctrl._ControlBase
*ctrl = AllocateStructure(_Label)
*ctrl\ptrMe = *ctrl
*ctrl\StructID = #Struct_Control_Label
ProcedureReturn *ctrl
EndProcedure
Procedure Free_Control(*Ctrl._ControlBase)
; Return 0 if the FreeStructure was done
; Return *Ctrl if FreeStructure was not done
; so we can use *MyControlPointer = Free_Control(*MyControlPointer)
#Free_Control = #PB_Compiler_Module + "::" + #PB_Compiler_Procedure
;Debug "FreeControl-Reference = " + #Free_Control
Protected ret
If *Ctrl
OnErrorGoto(?Free_Control_Error)
If *Ctrl = *Ctrl\ptrMe ; verify if it's valid Pointer one of our hirarchical Structures
If (*Ctrl\StructID & #Struct_Control_Base) = #Struct_Control_Base
FreeStructure(*Ctrl)
ProcedureReturn 0
Else
MessageRequester(#Free_Control, "Wrong Structure ID : Pointer is not a Control")
EndIf
Else
; here we get a Warning if we passed a value what is not a correct Pointer to our Structure
MessageRequester(#Free_Control, "Pointer validation Error: Pointer don't match with ptrMe!")
EndIf
Else
MessageRequester(#Free_Control, "Error: Pointer = 0")
EndIf
ProcedureReturn *Ctrl
Free_Control_Error:
MessageRequester(#Free_Control, "Fatal Pointer Error *ptr= " +Str(*Ctrl) + " ! It seems you passed a value not a Pointer!")
ProcedureReturn *Ctrl
EndProcedure
EndModule
Procedure ControlListIsNull(List *lst.Ctrls::_ControlBase())
Protected xIsNull = #True
ForEach *lst()
If *lst() <> 0
xIsNull = #False
Debug "NotNull"
Debug *lst()
Break
EndIf
Next
ProcedureReturn xIsNull
EndProcedure
Procedure Clear_ControlList(List *lst.Ctrls::_ControlBase())
If ControlListIsNull(*lst())
ClearList(*lst())
ProcedureReturn #True
Else
MessageRequester("ClearPointerList Error", "The PointerList you want to clear contains values <> 0")
EndIf
ProcedureReturn #False
EndProcedure
NewList *lstView1.Ctrls::_ControlBase()
NewList *lstView2.Ctrls::_ControlBase()
AddElement(*lstView1())
*lstView1()=Ctrls::Allocate_Button()
AddElement(*lstView1())
*lstView1()=Ctrls::Allocate_Label()
AddElement(*lstView2())
*lstView2()=Ctrls::Allocate_Label()
AddElement(*lstView2())
*lstView2()=Ctrls::Allocate_Label()
Debug "List lstView1"
ForEach *lstView1()
With *lstView1()
Debug Str(\ptrMe) + " : " + Str(\StructID)
EndWith
Next
Debug ""
Debug "List lstView2"
ForEach *lstView2()
With *lstView2()
Debug Str(\ptrMe) + " : " + Str(\StructID)
EndWith
Next
Debug ""
Debug "kill lstView1 - without Error!"
ForEach *lstView1()
*lstView1() = Ctrls::Free_Control(*lstView1())
Next
ForEach *lstView1()
Debug "*ptr = " + Str(*lstView1())
Next
If Clear_ControlList(*lstView1())
Debug "*lstView1() is cleard!"
Else
Debug "*lstView1() is not ready to clear - it's containg Pointers <> 0"
EndIf
Clear_ControlList(*lstView1())
; ATTENTION: To see the MessageBox with Error you have to run without Debugger
Define *WrongPointer = 1234567
Debug ""
Debug "kill lstView2 - Pointer=0!"
Ctrls::Free_Control(0)
Debug ""
Debug "kill lstView2 - Pointer validation Error!"
Define *mem
*mem = *lstView2()\ptrMe
*lstView2()\ptrMe = 0
*lstView2() = Ctrls::Free_Control(*lstView2())
Debug ""
Debug "kill lstView2 - Wrong Structure ID!"
*lstView2()\ptrMe = *mem
*lstView2()\StructID = 0
*lstView2() = Ctrls::Free_Control(*lstView2())
Debug ""
Debug "kill lstView2 - Error Wrong Pointer!"
Ctrls::Free_Control(*WrongPointer)
Clear_ControlList(*lstView2())
Re: FreeStructure() crash
Posted: Thu Oct 24, 2024 7:56 pm
by Joubarbe
Hmm, that's an approach. I would say that it's a bit of an overkill; it really complexifies the code too much to my taste. But it's interesting! Thank you for sharing.
Re: FreeStructure() crash
Posted: Fri Oct 25, 2024 2:07 pm
by SMaag
Joubarbe wrote: Thu Oct 24, 2024 7:56 pm
I would say that it's a bit of an overkill;
Yes exactly!
But a serious problems with pointer is a nightmare, especally if your programm runs long time without any error.
Your description of the problem sounds like such a nightmare pointer bug!
Here is a Demo how a little typing error or a small carlessness during programming process (maybe when your telefon rings!)
creates a serious bug.
Code: Select all
EnableExplicit
Structure TTest1
ValI.i
ValD.d
ValF.f
EndStructure
Structure TTest2
m1.l
m2.c
m3.i
m4.i
EndStructure
Procedure Test1(*tst.TTest1)
With *tst
\ValI = 10
\ValD = 20.0
\ValF = 30.0
EndWith
EndProcedure
Procedure Test2(*tst.TTest2)
With *tst
\m1 = 1
\m2 = 2
\m3 = 3
\m4 = 4
EndWith
EndProcedure
Define t1.TTest1
Define t2.TTest2
; here the Parameters are wrong.
; because PB is not TypeSafe we don't get any warning
Test1(t2) ; it's just a little typing error what will create total chaos
Test2(t1)
With t1
Debug ""
Debug "Structure TTest1"
Debug \ValI
Debug \ValD
Debug \ValF
EndWith
With t2
Debug ""
Debug "Structure TTest2"
Debug \m1
Debug \m2
Debug \m3
Debug \m4
EndWith
; Demo: Send a standard Int var to a pointer parameter!
; PB don't give any warning! That's like classic C
; with the same problems.
Define k=@t1 ; set k to address of t1, otherwise we produce a crash
Test1(k) ; in TypeSafe systems like Pascal this is not possible
; it's just to show what is possible and how fast there might be a serious problem!