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

[SR-12675] Runtime crash with certain generic constraints #55119

Open
NevinBR opened this issue Apr 27, 2020 · 5 comments
Open

[SR-12675] Runtime crash with certain generic constraints #55119

NevinBR opened this issue Apr 27, 2020 · 5 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@NevinBR
Copy link
Contributor

NevinBR commented Apr 27, 2020

Previous ID SR-12675
Radar None
Original Reporter @NevinBR
Type Bug
Environment

Swift 5.1.3
Xcode 11.3.1
MacOS 10.14.6

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee dfsweeney (JIRA)
Priority Medium

md5: 42fa052efdf3e8990b7d28bc1fee3b41

Issue Description:

When using a marker protocol to achieve dynamic dispatch (as described in the thread Test if a type conforms to a non-existential protocol on the Swift forums), if nontrivial constraints are place on the generic type, then the program will crash at runtime.

I'm sure this example could be made shorter, but it's what I have at the moment that demonstrates the crash. In particular, the constraint P.Wrapped.Element: Equatable is what causes the crash. Any non-trivial constraint there (ie. a constraint which is not also implied by the constraint on T in the action method) causes a crash.

Note that it is not necessary to actually use the Equatable conformance. We could make the action method simply return true, without ever calling ==, and it would still crash.

The point is, the constraint T == Wrapped is sufficient to let the program use the constraints on T when handling values of type Wrapped, but if there are additional constraints on Wrapped itself, the program crashes even though those constraints are only used on values of type Wrapped (or even not used at all!)

protocol AttemptIfBidirectional {
  associatedtype Wrapped
  associatedtype Result
  func action<T: BidirectionalCollection>(_ t: T.Type) -> Result where T == Wrapped
}

extension AttemptIfBidirectional {
  func attemptAction() -> Result? {
    BidirectionalMarker.attempt(self)
  }
}

protocol KnownBidirectional {
  static func performAction<T: AttemptIfBidirectional>(_ t: T) -> T.Result?
}

enum BidirectionalMarker<A: AttemptIfBidirectional> {
  static func attempt(_ a: A) -> A.Result? {
    (self as? KnownBidirectional.Type)?.performAction(a)
  }
}

extension BidirectionalMarker: KnownBidirectional where A.Wrapped: BidirectionalCollection {
  static func performAction<T: AttemptIfBidirectional>(_ t: T) -> T.Result? {
    (t as? A)?.action(A.Wrapped.self) as? T.Result
  }
}

protocol ProxyProtocol { associatedtype Wrapped }
enum Proxy<Wrapped>: ProxyProtocol {}


// If we remove the constraint `P.Wrapped.Element: Equatable`, and change the
// body of `action` so it doesn't call `==`, then there is no crash.
struct SameLastElements<P: ProxyProtocol>: AttemptIfBidirectional
  where P.Wrapped: Collection, P.Wrapped.Element: Equatable
{
  typealias Wrapped = P.Wrapped
  typealias Result = Bool

  var x: Wrapped
  var y: Wrapped

  init<T>(_ x: T, _ y: T) where P == Proxy<T> {
    self.x = x
    self.y = y
  }

  func action<T: BidirectionalCollection>(_ t: T.Type) -> Result where T == Wrapped {
    // crashes here, and `self` is unavailable
    if x.isEmpty || y.isEmpty { return false }
    return x.last == y.last
  }
}

func crasher() {
  let a = [1, 2, 3]
  let b = [2, 1, 3]
  let x = SameLastElements(a, b).attemptAction()
  print(x)
}

crasher()
@swift-ci
Copy link
Collaborator

swift-ci commented May 1, 2020

Comment by Daniel Sweeney (JIRA)

I broke this down a little, just to make sure I knew exactly where it was freaking out:

 let MaybeS = self as? KnownBidirectional.Type
    let S = MaybeS!
    //crash hard here, performAction uses generic type of a, a is SameLastElements<Proxy<[Int]>>
    // s is KnownBidirectional.Type, call should be to static performAction
    // where T is AttemptIfBidirectional
    let r = S.performAction(a)

Stepping in to the debugger in assembly at S.performAction(a)

In the debugger it's going to:

dis -s 0x1000013ac 
static BidirectionalMarker.attempt(_:): 

I don't know if that's the entry point, the debugger seems to drop me there when I step in. I stepped through this instruction by instruction until the next branch. Theres a short run that puts an address together, puts it in r9, and jumps there. It blows up at the jump.

The address in r9 is:

(lldb) dis -s 0x00000001000018e0
sr12675`protocol witness for static KnownBidirectional.performAction<A>(_:) in conformance <> BidirectionalMarker<A>:
  

I'm not sure if that's the entry point. You get a segmentation fault if you run it outside the debugger. In the debugger it makes lldb freak out, which is interesting. I'd like to look at this further if that's OK.

@swift-ci
Copy link
Collaborator

swift-ci commented May 5, 2020

Comment by Daniel Sweeney (JIRA)

Looking at this further, if you build with -sanitize=address,undefined and run you get:

UndefinedBehaviorSanitizer:DEADLYSIGNAL
==83583==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address (pc 0x7fff69e6cdf9 bp 0x7ffee80b54a0 sp 0x7ffee80b5480 T11625043)
==83583==The signal is caused by a READ memory access.
==83583==Hint: this fault was caused by a dereference of a high value address (see registers below).  Dissassemble the provided pc to learn which register value was used.
    #&#8203;0 0x7fff69e6cdf8 in Array.count.getter (libswiftCore.dylib:x86_64+0x12df8)
    #&#8203;1 0x7fff69e6f098 in protocol witness for Collection.endIndex.getter in conformance [A] (libswiftCore.dylib:x86_64+0x15098)
    #&#8203;2 0x7fff69e67bce in Collection.isEmpty.getter (libswiftCore.dylib:x86_64+0xdbce)
    #&#8203;3 0x7fff69e93535 in BidirectionalCollection.last.getter (libswiftCore.dylib:x86_64+0x39535)
    #&#8203;4 0x107b4de7c in SameLastElements.action<A>(_:) (main:x86_64+0x100004e7c)
    #&#8203;5 0x107b4e021 in protocol witness for AttemptIfBidirectional.action<A>(_:) in conformance SameLastElements<A> (main:x86_64+0x100005021)
    #&#8203;6 0x107b4bcbd in static BidirectionalMarker<>.performAction<A>(_:) (main:x86_64+0x100002cbd)
    #&#8203;7 0x107b4c43b in protocol witness for static KnownBidirectional.performAction<A>(_:) in conformance <> BidirectionalMarker<A> (main:x86_64+0x10000343b)
    #&#8203;8 0x107b4abf2 in static BidirectionalMarker.attempt(_:) (main:x86_64+0x100001bf2)
    #&#8203;9 0x107b4a416 in AttemptIfBidirectional.attemptAction() (main:x86_64+0x100001416)
    #&#8203;10 0x107b52c5c in crasher() (main:x86_64+0x100009c5c)
    #&#8203;11 0x107b52623 in main (main:x86_64+0x100009623)
    #&#8203;12 0x7fff6a830cc8 in start (libdyld.dylib:x86_64+0x1acc8)


==83583==Register values:
rax = 0x0000000000000000  rbx = 0x00007fff83a818f8  rcx = 0x0000000000000200  rdx = 0x0000000000000000  
rdi = 0x00007fff83a818f8  rsi = 0x00007fff83a818f8  rbp = 0x00007ffee80b54a0  rsp = 0x00007ffee80b5480  
 r8 = 0x00007fff6a1cdd54   r9 = 0x00000fffffffffff  r10 = 0x0000000000000000  r11 = 0xffffffffffffffff  
r12 = 0x00007ffee80b54e0  r13 = 0x00007ffee80b5740  r14 = 0xe000006060000003  r15 = 0xe000006060000003  
UndefinedBehaviorSanitizer can not provide additional info.
==83583==ABORTING
zsh: abort      ./main

  

(there are more frames there that I cut off.) If I have it right, r15 is the address, it's not mapped. I'm not sure what's populating it exactly.

Without address sanitization the debugger loses track of something important and crashes and you never get the backtrace, which is a problem. I was thinking there might be stack corruption itself but I don't think that's happening (the stack and frame pointers look OK, etc.) Another hypothesis is that we're losing one of the levels of indirection in the runtime. But it looks from the above like it's going to Array.count.getter, which matches the type of the input arrays.

The disassembly right around the fault is

    0x7fff69e6cdeb <+59>:  callq  0x7fff6a1450a0            ; _swift_isClassOrObjCExistentialType
    0x7fff69e6cdf0 <+64>:  andq   %r14, %r15
    0x7fff69e6cdf3 <+67>:  testb  $0x1, %al
    0x7fff69e6cdf5 <+69>:  cmoveq %r14, %r15
    0x7fff69e6cdf9 <+73>:  movq   0x10(%r15), %rax
 

where it calls isClassOrObjCExtistentialType, then does some things, then does a move off of r15 which fails, so possibly isClassOrObjCExtistentialType is not reporting back the truth for some reason. I have to think about how to test that.

@swift-ci
Copy link
Collaborator

swift-ci commented May 8, 2020

Comment by Daniel Sweeney (JIRA)

I just compiled and ran this in the 05-04 Swift 5.3 and 05-05 Swift Development toolchains and still got the runtime crash.

@swift-ci
Copy link
Collaborator

Comment by Daniel Sweeney (JIRA)

Continuing to work on this. Some notes:

  1. I haven't found another way to replicate the crash other than to retype the problem code above in different terms. I have put together a bunch of different patterns of code to try to fire this off but they all run OK, or fail to compile.

  2. The crash is because r13 (the self/context pointer in x86-64) is getting corrupted in performAction() in the marker class. It should be a pointer (to either self in an instance context or the type metadata in a static context) but it picks up a value off the stack and puts sets the register to a non-pointer value.

  3. After that things run for a bit, through a couple of calls, until somebody tries to use self or Type and then you get the crash. That's why it is hard to trace and why there's no self available in the .action method.

So the prolog code in .performAction believes that there's a pointer in one of the stack variables, but is wrong. Those are the symptoms but I'm still not sure what is the cause and where is the fix.

Something is losing track of what or where it is. Examining the SIL and IR along with the assembly is not telling me much. There's nothing obviously missing. I'm reasoning through the pattern of abstraction in the code trying to see if something is going awry somewhere. Some speculations are that the call chain from

  1. instance method conforming to protocol

  2. instance method in extension

  3. static method in extension

  4. static method in witness table

  5. static method in extension

is losing track either because of the instance->static transition or protocol->extension transition. But that does not explain why the problem occurs with the generic constraints only. I'm missing something here.

@swift-ci
Copy link
Collaborator

Comment by Daniel Sweeney (JIRA)

This one is still crashing in Xcode 12.0 with the Xcode 12.0 toolchain. I am still working on this although it has been a while since I have had anything to log here.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

2 participants