Borrow pointers; Memory Safety; Rust/C++;

Everything else that doesn't fall into one of the other PB categories.
HanPBF
Enthusiast
Enthusiast
Posts: 564
Joined: Fri Feb 19, 2010 3:42 am

Borrow pointers; Memory Safety; Rust/C++;

Post by HanPBF »

Bjarne Stroustrup explained in a presentation how C++ gets memory safe when using a borrowing like concept as in Rust.
He also said, that beyond memory safety also files, etc. must be closed and tidied up. So garbage collection does not help here.

How would I use PureBasic pointers to stuctures or memory in a way, that I can say which part of the program is when resoponsible or
has the pointer in its area of control/responsibility?

I know that variables get freed after leaving a procedure.
But when I let a procedure create a structure for further calculation it is given from one to another procedure.

Any links to already discussed topics or any ideas really apreciated.
Thanks!
User avatar
mk-soft
Always Here
Always Here
Posts: 5408
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Borrow pointers; Memory Safety; Rust/C++;

Post by mk-soft »

When a procedure create structure data, there are two possibilities.

1. The data is static and is released by PB when the program is terminated.
2. The caller releases the data after use.

Example:

Code: Select all


Structure udtData
  iVal.i
  sVal.s
EndStructure

Procedure myFunctionStatic()
  Static *pData.udtData
  
  If Not *pData
    *pData = AllocateStructure(udtData)
  EndIf
  If *pData
    *pData\iVal = 100
    *pData\sVal = "Hello World!"
  EndIf
  ProcedureReturn *pData
EndProcedure

Procedure myFunctionData()
  Protected *pData.udtData
  
  *pData = AllocateStructure(udtData)
  If *pData
    *pData\iVal = 200
    *pData\sVal = "Hello World!"
  EndIf
  ProcedureReturn *pData
EndProcedure

Define *data1.udtData, *data2.udtData

*data2 = myFunctionData()
Debug *data2\iVal
*data2 = myFunctionData() ; <- MemoryLeak !!! because forget free structure
Debug *data2\iVal
FreeStructure(*data2)

*data1 = myFunctionStatic()
Debug *data1\iVal
*data1 = myFunctionStatic()
Debug *data1\iVal

FreeStructure(*data1) ; <- do not with static data
*data1 = myFunctionStatic() ; <- Crash because destroyed static data
Debug *data1\iVal
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
tored
User
User
Posts: 61
Joined: Wed Feb 16, 2022 12:47 pm
Location: Sweden

Re: Borrow pointers; Memory Safety; Rust/C++;

Post by tored »

Nothing that is built in today for PureBasic, however I guess it is theoretically possible to use GCC extensions with the C backend to hook into advanced features to track these kind of things, e.g. there exists a proof of concept smart pointer C library that uses this functionality to clean up resources when pointer is no longer in use.

https://github.com/Snaipe/libcsptr

What I do instead is a custom module that when in debug mode tracks memory allocations, file handlers, etc and reports if anything is still open when the application closes, primitive but works well.

Edit: I think the GCC extension in question is the __attribute__ cleanup

https://github.com/Snaipe/libcsptr/blob ... C32-L44C39

https://gcc.gnu.org/onlinedocs/gcc/Comm ... -attribute
HanPBF
Enthusiast
Enthusiast
Posts: 564
Joined: Fri Feb 19, 2010 3:42 am

Borrow pointers; Memory Safety; Rust/C++;

Post by HanPBF »

@mk-soft, thanks for the example to clarify things.

@tored, thanks a lot for the links and the info
What I do instead is a custom module that when in debug mode tracks memory allocations, file handlers, etc and reports if anything is still open when the application closes, primitive but works well.
Is this as simple as it reads: You simply store all pointers in a list for example with 1 in use, 0 destroyed and at the end they sum up as 0 or something went wrong?
tored
User
User
Posts: 61
Joined: Wed Feb 16, 2022 12:47 pm
Location: Sweden

Re: Borrow pointers; Memory Safety; Rust/C++;

Post by tored »

HanPBF wrote: Tue Feb 27, 2024 1:07 pm Is this as simple as it reads: You simply store all pointers in a list for example with 1 in use, 0 destroyed and at the end they sum up as 0 or something went wrong?
Yes, the general idea is to override PureBasic library procedures with macros and and add whatever you are tracking to a list.

Here is my current implementation, at the moment I only have support for memory and files, but it is easy to add whatever needed (it is thread safe). I use a module for this because all my code is module based, however the drawback is that you need to remember to do UseModule Tracker before use.

The inspiration for this came from this thread https://www.purebasic.fr/english/viewto ... 12&t=39168

After this code segment I add an example.

Code: Select all

DeclareModule Tracker
  EnableExplicit
  
  CompilerIf #PB_Compiler_Debugger
    Macro AllocateStructureProxy(name, command = AllocateStructure)
      command(name)  
    EndMacro
    
    Macro AllocateMemory(size, flags = #Null)
      Tracker::__TrackAllocateMemory(size, flags, #PB_Compiler_File, #PB_Compiler_Line)
    EndMacro
    
    Macro ReAllocateMemory(mem, size, flags = #Null)
      Tracker::__TrackReAllocateMemory(mem, size, flags, #PB_Compiler_File, #PB_Compiler_Line)
    EndMacro
    
    Macro FreeMemory(mem)
      Tracker::__TrackFreeMemory(mem)
    EndMacro
    
    Macro AllocateStructure(name)
      Tracker::__TrackAllocateStructure(AllocateStructureProxy(name), SizeOf(name), #PB_Compiler_File, #PB_Compiler_Line)
    EndMacro
    
    Macro FreeStructure(mem)
      Tracker::__TrackFreeStructure(mem)
    EndMacro
    
    Macro CreateFile(file, filename, flags = #Null)
      Tracker::__TrackCreateFile(file, filename, flags, #PB_Compiler_File, #PB_Compiler_Line)
    EndMacro  
    
    Macro OpenFile(file, filename, flags = #Null)
      Tracker::__TrackOpenFile(file, filename, flags, #PB_Compiler_File, #PB_Compiler_Line)
    EndMacro
    
    Macro ReadFile(file, filename, flags = #Null)
      Tracker::__TrackReadFile(file, filename, flags, #PB_Compiler_File, #PB_Compiler_Line)
    EndMacro
    
    Macro CloseFile(file)
      Tracker::__TrackCloseFile(file)
    EndMacro
    
    Declare __TrackAllocateMemory(size.q, flags, trackFile.s, trackLine.i)
    Declare __TrackReAllocateMemory(*mem, size.q, flags, trackFile.s, trackLine.i)
    Declare __TrackFreeMemory(*mem)
    Declare __TrackAllocateStructure(*mem, size.q, trackFile.s, trackLine.i)
    Declare __TrackFreeStructure(*mem)
    Declare __TrackCreateFile(file, filename.s, flags, trackFile.s, trackLine.i)
    Declare __TrackOpenFile(file, filename.s, flags, trackFile.s, trackLine.i)
    Declare __TrackReadFile(file, filename.s, flags, trackFile.s, trackLine.i)
    Declare __TrackCloseFile(file)
  CompilerEndIf
  
  Declare PrintAllocations()
EndDeclareModule

Module Tracker
  CompilerIf #PB_Compiler_Debugger
    
    CompilerIf #PB_Compiler_Thread 
      Global mutex = CreateMutex()
      
      Macro Lock()
        LockMutex(mutex)
      EndMacro  
      
      Macro Unlock()
        UnlockMutex(mutex)
      EndMacro
      
    CompilerElse  
      Macro Lock()
      EndMacro  
      
      Macro Unlock()
      EndMacro
    CompilerEndIf  
    
    Macro AllocateMemoryProxy(size, flags, command = AllocateMemory)
      command(size, flags)
    EndMacro
    
    Macro ReAllocateMemoryProxy(mem, size, flags, command = ReAllocateMemory)
      command(mem, size, flags)
    EndMacro
    
    Macro FreeMemoryProxy(mem, command = FreeMemory)
      command(mem)
    EndMacro
    
    Macro FreeStructureProxy(mem, command = FreeStructure)
      command(mem)
    EndMacro
    
    Structure MemoryAllocation
      *mem
      size.q
      trackFile.s
      trackLine.i
    EndStructure
    Global NewList memoryAllocations.MemoryAllocation()
    
    Procedure TrackMemoryAllocation(*mem, size, trackFile.s, trackLine.i)
      Lock()
      If *mem And AddElement(memoryAllocations())
        memoryAllocations()\mem = *mem
        memoryAllocations()\size = size
        memoryAllocations()\trackFile = trackFile
        memoryAllocations()\trackLine = trackLine
      EndIf
      Unlock() 
    EndProcedure  
    
    Procedure __TrackAllocateMemory(size.q, flags, trackFile.s, trackLine.i)
      Protected *mem = AllocateMemoryProxy(size, flags)
      TrackMemoryAllocation(*mem, size, trackFile, trackLine)
      ProcedureReturn *mem
    EndProcedure
    
    Procedure __TrackReAllocateMemory(*mem, size.q, flags, trackFile.s, trackLine.i)
      Protected *new = ReAllocateMemoryProxy(*mem, size, flags)
      If *new
        Lock()
        ForEach memoryAllocations()
          If memoryAllocations()\mem = *mem
            DeleteElement(memoryAllocations())
            Break
          EndIf
        Next
        Unlock() 
        TrackMemoryAllocation(*new, size, trackFile, trackLine)
      EndIf
      ProcedureReturn *new
    EndProcedure
    
    Procedure __TrackFreeMemory(*mem)
      Lock()
      ForEach memoryAllocations()
        If memoryAllocations()\mem = *mem
          DeleteElement(memoryAllocations())
          Break
        EndIf
      Next
      Unlock() 
      FreeMemoryProxy(*mem)
    EndProcedure
    
    Procedure __TrackAllocateStructure(*mem, size.q, trackFile.s, trackLine.i)
      TrackMemoryAllocation(*mem, size, trackFile, trackLine)
      ProcedureReturn *mem
    EndProcedure
    
    Procedure __TrackFreeStructure(*mem)
      Lock()
      ForEach memoryAllocations()
        If memoryAllocations()\mem = *mem
          DeleteElement(memoryAllocations())
          Break
        EndIf
      Next
      Unlock() 
      FreeStructureProxy(*mem)
    EndProcedure
    
    
    Macro CreateFileProxy(file, filename, flags, command = CreateFile)
      command(file, filename, flags)
    EndMacro  
    
    Macro OpenFileProxy(file, filename, flags, command = OpenFile)
      command(file, filename, flags)
    EndMacro  
    
    Macro ReadFileProxy(file, filename, flags, command = ReadFile)
      command(file, filename, flags)
    EndMacro
    
    Macro CloseFileProxy(file, command = CloseFile)
      command(file)  
    EndMacro
    
    Structure FileAllocation
      file.i
      filename.s
      trackFile.s
      trackLine.i
    EndStructure
    Global NewList fileAllocations.FileAllocation()
    
    Procedure TrackFileOperation(id, file, filename.s, trackFile.s, trackLine.i)
      Lock()
      If id And AddElement(fileAllocations())
        If file = #PB_Any
          fileAllocations()\file = id
        Else
          fileAllocations()\file = file
        EndIf
        fileAllocations()\filename = filename
        fileAllocations()\trackFile = trackFile
        fileAllocations()\trackLine = trackLine
      EndIf
      Unlock()
    EndProcedure  
    
    Procedure __TrackCreateFile(file, filename.s, flags, trackFile.s, trackLine.i)
      Protected id = CreateFileProxy(file, filename, flags)
      TrackFileOperation(id, file, filename, trackFile, trackLine)
      ProcedureReturn id
    EndProcedure
    
    Procedure __TrackOpenFile(file, filename.s, flags, trackFile.s, trackLine.i)
      Protected id = OpenFileProxy(file, filename, flags)
      TrackFileOperation(id, file, filename, trackFile, trackLine)
      ProcedureReturn id
    EndProcedure
    
    Procedure __TrackReadFile(file, filename.s, flags, trackFile.s, trackLine.i)
      Protected id = ReadFileProxy(file, filename, flags)
      TrackFileOperation(id, file, filename, trackFile, trackLine)
      ProcedureReturn id
    EndProcedure
    
    Procedure __TrackCloseFile(file)
      Lock()
      If LastElement(fileAllocations())
        Repeat
          If fileAllocations()\file = file
            DeleteElement(fileAllocations())
            Break
          EndIf
        Until PreviousElement(fileAllocations()) = #False
        LastElement(fileAllocations())
      EndIf
      Unlock()
      
      CloseFileProxy(file)
    EndProcedure
  CompilerEndIf
  
  Procedure PrintAllocations()
    CompilerIf #PB_Compiler_Debugger
      Lock()
      If ListSize(memoryAllocations())
        Debug "-[ Allocated Memory ]------"
        ForEach memoryAllocations()
          Debug memoryAllocations()\trackFile + 
                ":" + memoryAllocations()\trackLine + 
                " Size: " + memoryAllocations()\size + 
                " Address: " + memoryAllocations()\mem
        Next
        Debug "---------------------------"
      EndIf
      
      If ListSize(fileAllocations())
        Debug "-[ Allocated Files ]------"
        ForEach fileAllocations()
          Debug fileAllocations()\trackFile + 
                ":" + fileAllocations()\trackLine + 
                " File: " + fileAllocations()\filename + 
                " Id: " + fileAllocations()\file
        Next
        Debug "---------------------------"
      EndIf
      Unlock()
    CompilerEndIf
  EndProcedure
EndModule

Code: Select all

DeclareModule MyModule
  EnableExplicit
  UseModule Tracker
  
  Declare DoStuff()
EndDeclareModule  

Module MyModule
  
  Procedure DoStuff()
    Protected *mem = AllocateMemory(100)
    FreeMemory(*mem)
    
    *mem = AllocateMemory(100) ; <-- missing free
    
    ReadFile(0,#PB_Compiler_File)    
    CloseFile(0)
    
    ReadFile(0 ,#PB_Compiler_File)    ; <-- missing close
  EndProcedure  
EndModule  


MyModule::DoStuff()

Tracker::PrintAllocations()  ; <-- should print two leaks
tored
User
User
Posts: 61
Joined: Wed Feb 16, 2022 12:47 pm
Location: Sweden

Re: Borrow pointers; Memory Safety; Rust/C++;

Post by tored »

Can you mix PB macros and inline C?

Code: Select all

EnableExplicit

CompilerIf #PB_Compiler_Backend <> #PB_Backend_C
  CompilerError "Use C backend"
CompilerEndIf

Procedure Defer(*p)
  Debug "defer"
EndProcedure  

Procedure DoStuff()
  ! int p __attribute__((cleanup(f_defer)));
  Debug "do stuff"
EndProcedure  

DoStuff()

End
Defer(#Null)

tored
User
User
Posts: 61
Joined: Wed Feb 16, 2022 12:47 pm
Location: Sweden

Re: Borrow pointers; Memory Safety; Rust/C++;

Post by tored »

:mrgreen:

Code: Select all

EnableExplicit

Macro Defer(__deferexpression)
  Define __deferloop#MacroExpandedCount
  While __deferloop#MacroExpandedCount = 0 Or __deferexpression
  __deferloop#MacroExpandedCount + 1
EndMacro  

Macro EndDefer
  Wend  
EndMacro

Procedure Free(*mem)
  Debug  "Free: " + PeekS(*mem)
  FreeMemory(*mem)
EndProcedure  

Procedure Test()
  Protected *mem = AllocateMemory(100)
  Defer(Free(*mem))
    PokeS(*mem, "Hello World")
  EndDefer
EndProcedure

Test()
Post Reply