စံကား

08Pointers and the heap

Pointer mistakes

The mistakes pointers make possible, and how Zaka catches each one.

Pointers are powerful because each one reaches straight into RAM, and that same power makes them simple to misuse. In a lot of other languages, a RAM mistake corrupts the program without warning. Zaka catches these mistakes twice. The compiler finds each one it can prove from the text of the program, and does not run the program. The Zaka VM checks each read and write through a pointer while the program runs, stops the program at the first mistake, and tells what went wrong.

Each section below shows both. With --mem-check warn, the compiler reports the mistake as a warning and the program runs, so the VM's own check shows.

Reading through a null pointer

စံကားZK0709

Expected error

error[ZK0709]: "ပ" is ဗလာ here, so it cannot be dereferenced

With --mem-check warn, or Warn in the Explorer, the program runs and the VM stops it:

စံကား

Expected output

အစ
the program stopped: access through a null address at 0x0

Give the pointer an address first, or test it against ဗလာ.

Reading past the end

A block of 4 integers has boxes 0 to 3. Box 4 is outside it:

စံကားZK0710

Expected error

error[ZK0710]: offset 4 is past the end of a block of 4 elements

With --mem-check warn, or Warn in the Explorer, the program runs and the VM stops it:

စံကား

Expected output

၀
the program stopped: access of 8 octets at 0x101040 lands outside each live block

A new block starts out filled with zeros, so box 3 prints ၀. Count from 0 and stop one before the size. The compiler sees this mistake because the size and the box number are both written in the program. When one of them comes from input, the VM's check is the one that catches it.

Reading past a variable

Each variable has a block of its own, with a gap after it, just like the heap blocks above. A borrow of a variable points at a block of one box, so a step past it lands in the gap:

စံကားZK0710

Expected error

error[ZK0710]: offset 1 is past the end of a block of 1 elements

With --mem-check warn, or Warn in the Explorer, the program runs and the VM stops it:

စံကား

Expected output

the program stopped: access of 8 octets at 0x1008 lands outside each live block

Checked and packed variables

The Zaka VM can set out variables in two forms:

ModeWhere the variables goA step past a variable
checked, the defaulteach in a block of its own, with a gap after itstops the program
packedthe locals of a call next to each other in one block, as C does, and the globals in one morereads or changes the next variable

Run a program with zakac run --mem-check warn --frames packed, or pick Warn for Ownership check and Packed, as C for Variables in the Explorer's settings. The program above then prints ၂, the value of ခ, and goes on as if nothing were wrong.

Packed is how C sets out variables, and how native Zaka programs will. There, a step past a variable changes its neighbour with no warning, and the program goes wrong far from the line at fault. Heap blocks keep their checks in both modes, and so does a step past the whole block of a call. Keep the checked mode while learning; switch to packed to see what the checks catch.

Using a block after giving it back

မိတ္တူ borrows the block of ပ, and ပ gives the block back while မိတ္တူ still has a read to do:

စံကားZK0704

Expected error

error[ZK0704]: the block of "ပ" is freed here, but "မိတ္တူ" still borrows it and is used later

The message points at the give-back, and at the later read of မိတ္တူ as well. With --mem-check warn, or Warn in the Explorer, the program runs and the VM stops it:

စံကား

Expected output

the program stopped: access at 0x101040 inside a freed block

Swap the last two lines, so the read comes before the give-back, and the program prints ၇. Resizing the block while a borrow of it is still in use is the same mistake, since the block can move.

Declared as a plain ကိန်းပြည့် လိပ်စာ, မိတ္တူ would take the block from ပ instead of borrowing it. That program is correct: ပ holds ဗလာ after the move, so giving it back does nothing, and မိတ္တူ owns the block and gives it back itself.

A function that returns a borrow of its own variable would leave its caller with the same fault. The compiler refuses that function: see how long an address lasts.

Giving a block back twice

စံကားZK0702

Expected error

error[ZK0704]: the block of "ပ" is freed here, but "မိတ္တူ" still borrows it and is used later
error[ZK0702]: a borrowed address cannot be freed or resized; free its owner instead

The last line gives the block back through a borrow, which is never allowed: the owner alone gives a block back (ZK0702). The line before it is wrong as well, because the last line still uses မိတ္တူ (ZK0704). With --mem-check warn, or Warn in the Explorer, the program runs and the VM stops it:

စံကား

Expected output

the program stopped: the block at 0x101040 was freed before

Giving back ပ a second time would be harmless, because the first time set it to null.

Giving back a block that was not asked for

Blocks from နေရာယူ၍ can be given back; a variable's own storage cannot. A pointer at a variable is a borrow, and a borrow never gives a block back:

စံကားZK0702

Expected error

error[ZK0702]: a borrowed address cannot be freed or resized; free its owner instead

With --mem-check warn, or Warn in the Explorer, the program runs and the VM stops it:

စံကား

Expected output

the program stopped: 0x1000 is not the start of a live heap block

An address inside a heap block, such as ပ + ၂, is a borrow too, so giving it back is the same error.

After giving a block to a function

A function whose parameter is a plain ကိန်းပြည့် လိပ်စာ takes the block. After the call, the caller's variable holds ဗလာ:

စံကားZK0701

Expected error

error[ZK0701]: "ပ" was moved to another owner and now holds ဗလာ

With --mem-check warn, or Warn in the Explorer, the program runs and the VM stops it at the read of ဗလာ:

စံကား

Expected output

the program stopped: access through a null address at 0x0

In C, this is a use after free hidden inside another function. In Zaka the header tells: ရှင်းပစ် takes the block. To keep the block, give the parameter a ငှားထားသော kind and lend the block with ပ ၏ တန်ဖိုးအငှား. Then ရှင်းပစ် cannot give it back.

Losing a block

A block whose last owner is overwritten, or ends with its function, can never be given back:

စံကားZK0708

Expected error

error[ZK0708]: the block that "ပ" owns is never freed or moved on this path
error[ZK0708]: the block that "ပ" owns is never freed or moved on this path

The first message is for the second နေရာယူ၍, which overwrites the owner of the first block. The second is for the end of စမ်း, where ပ still holds the second block. With --mem-check warn, or Warn in the Explorer, the program runs to its end, since a lost block does no harm at once:

စံကား

Expected output

၀

Called in a loop, though, such a function eats the heap a little at a time. The compiler checks each overwrite of an owner and the end of each function. A block that a top-level variable still holds when the program ends is for zakac run --leaks, which lists each forgotten block. samples/mistakes/leak.zaka asks for a block three times and gives back the last one alone:

$ zakac run --mem-check warn --leaks samples/mistakes/leak.zaka
warning[ZK0708]: the block that "ပ" owns is never freed or moved on this path
 --> samples/mistakes/leak.zaka:4:1
  |
4 | မင်မိုရီ ကိန်းပြည့် ၂ ခုစာ နေရာယူ၍ လိပ်စာကို ပ တွင် သိမ်းထားမည်။
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
၁၀
၂၀
၃၀
leak: block at 0x101040, 16 octets
leak: block at 0x101060, 16 octets

Mistakes the compiler catches

Some mistakes are visible before the program runs, and the compiler reports them. The ZK04 codes are about kinds; the ZK07 codes come from the check of ownership and borrowing:

CodeMistake
ZK0410assigning to something that is not a variable or a value through a pointer
ZK0411taking the address of something that has none, such as ၅
ZK0412taking the address of a constant or a counted-loop variable
ZK0413reading through a value that is not a pointer
ZK0414reading through, or adding to, an opaque လိပ်စာ
ZK0415using an operator such as * or / on a pointer
ZK0416comparing or subtracting pointers of different kinds
ZK0417a heap statement given something other than a pointer
ZK0701using an owner after its block moved to another owner
ZK0702giving back or resizing a block through a borrow
ZK0703keeping a borrow in a heap block, behind a pointer, or in a top-level variable from a function
ZK0704giving back, resizing or moving a block while a borrow of it is still in use
ZK0705taking the address of a borrow
ZK0706returning a borrow of a function's own variable
ZK0707a call that can give back a block through one argument while another argument reaches the same block
ZK0708losing a block: its owner is overwritten, or its function ends, while it holds the block
ZK0709reading or writing through a pointer that is ဗလာ on each path to the read
ZK0710a box or cell number past the end of its block
ZK0711a warning, with --mem-strict alone: using a pointer that can be ဗလာ
ZK0712subtracting pointers into different blocks
ZK0713ငှားထားသော in the wrong place in a pointer kind
ZK0714a borrow given where an owner is wanted
ZK0715an owner given where a borrow is wanted
ZK0716lending a raw address with တန်ဖိုးအငှား
ZK0717a raw address kind under --no-raw
စံကားZK0413

Expected error

error[ZK0413]: isize is not a pointer and cannot be dereferenced

Hands on

  • Introduce a new fault into each program on this page, and read the message.
  • Fix the "past the end" program so it prints all four boxes and nothing more.
  • Run the "past a variable" program with --mem-check warn --frames packed. Then write ၉ through ပ + ၁ and print ခ. Which variable changed?
  • Set Ownership check to Warn in the Explorer's settings, and step through each program on this page to the line where the VM stops it.
  • Write a function that asks for a block and returns its address. Is that a mistake? (No: the caller owns the block from then on.)

Next: Lists.