Skip to content

interp: keep the byte layout of heap objects with uintptr pointers - #5911

Draft
davecheney wants to merge 1 commit into
tinygo-org:devfrom
davecheney:dfc/interp-raw-pointer-layout
Draft

davecheney wants to merge 1 commit into
tinygo-org:devfrom
davecheney:dfc/interp-raw-pointer-layout

Conversation

@davecheney

Copy link
Copy Markdown
Contributor

When interp writes a heap object back as a new global, it uses the object type or the layout type of the object. If the program stores a pointer in the object through a uintptr, this type can be incorrect:

  • A pointer-free layout gives the type [N x i8]. A pointer cannot go in an i8 field, thus the build fails with "interp: ptrtoint integer size does not equal pointer size".
  • A pointer at an offset that is not aligned to a pointer field goes across two fields. The build fails, or the pointer becomes 0.

Also, rawLLVMValue made a struct that is not packed. Thus a pointer that is not aligned moved to the next aligned offset, and the size of the value changed. A run of pointer words that is not a whole pointer gave a pointer that is not correct.

This change adds rawValue.fitsType. It is true if each pointer in the buffer is a whole pointer in a pointer-sized field of the type. If it is false, interp makes a packed type from the buffer (rawLLVMType). This type has the same size and the same bytes as the buffer, and a pointer field at the offset of each pointer. rawLLVMValue now uses this type. A part of a pointer gives an error and does not give an incorrect constant. Interp makes the type before it creates the global, thus an object that points to itself is safe. Normal programs do not change: the code size of testdata/init.go, map.go, reflect.go and stdlib.go for cortex-m-qemu is the same.

Tests:

  • interp/testdata/rawlayout.ll: heap objects with pointer-free and pointer layouts that hold an aligned, misaligned, self or straddling uintptr pointer.
  • interp/rawlayout_test.go: the size and the value of rawLLVMValue, and the error for a part of a pointer.
  • testdata/init_rawlayout.go: compares the init result with the same bytes made at runtime.

Reproducer. Go prints "heap: true". TinyGo fails to build. The bug is also in v0.43.0.

package main

import "unsafe"

var x int
var h = newH()

func newH() *[2]uintptr {
	p := new([2]uintptr)
	p[1] = uintptr(unsafe.Pointer(&x))
	return p
}

func main() {
	println("heap:", h[1] == uintptr(unsafe.Pointer(&x)))
}

When interp writes a heap object back as a new global, it uses the
object type or the layout type of the object. If the program stores
a pointer in the object through a uintptr, this type can be
incorrect:

- A pointer-free layout gives the type [N x i8]. A pointer cannot go
  in an i8 field, thus the build fails with "interp: ptrtoint integer
  size does not equal pointer size".
- A pointer at an offset that is not aligned to a pointer field goes
  across two fields. The build fails, or the pointer becomes 0.

Also, rawLLVMValue made a struct that is not packed. Thus a pointer
that is not aligned moved to the next aligned offset, and the size
of the value changed. A run of pointer words that is not a whole
pointer gave a pointer that is not correct.

This change adds rawValue.fitsType. It is true if each pointer in the
buffer is a whole pointer in a pointer-sized field of the type. If it
is false, interp makes a packed type from the buffer (rawLLVMType).
This type has the same size and the same bytes as the buffer, and a
pointer field at the offset of each pointer. rawLLVMValue now uses
this type. A part of a pointer gives an error and does not give an
incorrect constant. Interp makes the type before it creates the
global, thus an object that points to itself is safe. Normal programs
do not change: the code size of testdata/init.go, map.go, reflect.go
and stdlib.go for cortex-m-qemu is the same.

Tests:
- interp/testdata/rawlayout.ll: heap objects with pointer-free and
  pointer layouts that hold an aligned, misaligned, self or
  straddling uintptr pointer.
- interp/rawlayout_test.go: the size and the value of rawLLVMValue,
  and the error for a part of a pointer.
- testdata/init_rawlayout.go: compares the init result with the
  same bytes made at runtime.

Reproducer. Go prints "heap: true". TinyGo fails to build. The bug
is also in v0.43.0.

    package main

    import "unsafe"

    var x int
    var h = newH()

    func newH() *[2]uintptr {
    	p := new([2]uintptr)
    	p[1] = uintptr(unsafe.Pointer(&x))
    	return p
    }

    func main() {
    	println("heap:", h[1] == uintptr(unsafe.Pointer(&x)))
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant