Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

let a = someConst.unsafeAddr is very buggy and should be replaced #16794

Open
timotheecour opened this issue Jan 22, 2021 · 3 comments
Open

let a = someConst.unsafeAddr is very buggy and should be replaced #16794

timotheecour opened this issue Jan 22, 2021 · 3 comments
Labels
const `const x=expr` or `static: stmt` Language Design

Comments

@timotheecour
Copy link
Member

timotheecour commented Jan 22, 2021

this recent PR #15466 enabled one to write let a = someConst.unsafeAddr; however this feature is very buggy, not documented, had 0 tests in the PR that introduced it and should be reverted; see upcoming proposal for a better and simpler way to solve nim-lang/RFCs#257 that has none of its drawbacks

Example 1

unsafeAddr doesn't work with most types

when true:
  template fn(a) =
    const b = a
    let c = b.unsafeAddr
  type A = object
    a0: int
  type B = ref A
  fn [1,2]
  fn @[1,2]
  fn A(a0:1)
  # these don't work: Error: expression has no address
  # fn "ab"
  # fn (1, 2)
  # fn 1
  # fn B(a0:1) # this one would work with const ref PR https://github.com/nim-lang/Nim/pull/15528

note that these would work with let c = b instead of let c = b.unsafeAddr

Example 2

when true:
  const a = [1,2]
  proc fn =
    let a1 = a.unsafeAddr
    a1[][0] = 7
    echo a1[]
    echo a
  static: fn()
  fn()

vm:
[7, 2] # modified from [1,2]
[1, 2] # unmodified from [1,2]: inconsistent
rt:
SIGBUS

Example 3

# sub.nim
when true:
  const a* = [1,2]
  proc fn1* =
    let a1 = a.unsafeAddr
    let a2 = a.unsafeAddr
    echo cast[int](a1)
    echo cast[int](a2)

# main.nim
when true:
  import sub
  proc fn2* =
    let a1 = a.unsafeAddr
    let a2 = a.unsafeAddr
    echo cast[int](a1)
    echo cast[int](a2)
  fn1()
  fn2()

prints:
4416151424
4416151424
4416151440 # address of a returned is different depending on which module it was called from
4416151440

and this is generated in each module:

static NIM_CONST tyArray__HU7qaqKu9czJLT84iCBJnsA TM__CLoBuXEHTlg1in6VU6Aqdg_2 = {((NI) 1),
((NI) 2)}

unlike what was required in nim-lang/RFCs#257

Example 4

unsafeAddr still doesn't help with types (including arrays) that (transitively) contain ref/ptr; it still needs #15528

Example 5

when defined case10:
  const a = @[1,2]
  proc fn* =
    let a1 = a.unsafeAddr
    let a2 = a.unsafeAddr
    echo cast[int](a1)
    echo cast[int](a2)
  static: fn()
  fn()

vm:
4378615304
4378615368 # different from previous line (inconsistent)
rt:
4382200032
4382200032 # same from previous line: inconsistent

Example 6

when true:
  const a1 = @[1,2]
  const a2 = [1,2]
  proc fn* =
    let b1 = a1.unsafeAddr
    echo cast[int](b1) # ok
    let b2 = a2.unsafeAddr
    echo cast[int](b2) # Error: unhandled exception: 'intVal' is not accessible using discriminant 
  static: fn()

Example 7

when true:
  const a = @[1,2]
  let b = a.unsafeAddr
  proc fn1 =
    b[][0] = 7
    echo b[]
  fn1()

nim r main: SIGBUS
nim r -b:cpp main: prints @[7, 2]

Example 8

when true:
  const a = [1,2]
  let b = a.unsafeAddr
  proc fn1 =
    b[][0] = 7
    echo b[]
  fn1()

nim r main: prints [1, 2] with -d:danger, or SIGBUG without
nim r -b:cpp main: prints [7, 2] with or without -d:danger

Additional Information

1.5.1 2b5841c

links

@Clyybber
Copy link
Contributor

Clyybber commented Jan 22, 2021

Example 2, 7 and 8 are invalid because you are using unsafeAddr to mutate an immutable location. (which is undefined behaviour, but you know that..)
Example 6 is not related and happens with let too, because it's a VM issue:

static:
  let a1 = @[1,2]
  let a2 = [1,2]
  proc fn =
    let b1 = a1.unsafeAddr
    echo cast[int](b1) # ok
    let b2 = a2.unsafeAddr
    echo cast[int](b2) # Error: unhandled exception: 'intVal' is not accessible using discriminant 
  fn()

Please remove them from the issue so that it doesn't distract from actual issues like as the one demonstrated in Example 3.

@timotheecour
Copy link
Member Author

timotheecour commented Jan 27, 2021

Example 2, 7 and 8 are invalid because you are using unsafeAddr to mutate an immutable location. (which is undefined behaviour, but you know that..)

that's the point, #15466 introduced unsafeAddr as way to implement nim-lang/RFCs#257 but as I'm showing with those examples, it creates an un-necessary unsafe loophole in the type system. My point is that nim-lang/RFCs#257 can be fixed in a simpler way without resorting to unsafeAddr and its associated issues; I haven't written the RFC yet but see the draft in timotheecour#553

@Clyybber
Copy link
Contributor

Clyybber commented Jan 27, 2021

There is no loophole. The "unsafe" is in the name. You must ensure not to modify the location, just as when you take the unsafeAddr of a let symbol.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
const `const x=expr` or `static: stmt` Language Design
Projects
None yet
Development

No branches or pull requests

2 participants