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-6652] "large space" heuristic might be too aggressive in certain cases. #49201

Closed
xedin opened this issue Dec 20, 2017 · 6 comments
Closed
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@xedin
Copy link
Member

xedin commented Dec 20, 2017

Previous ID SR-6652
Radar None
Original Reporter @xedin
Type Bug
Status Resolved
Resolution Done
Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee @CodaFi
Priority Medium

md5: fc2616ac13e3864d7d4a93d3bcb25982

relates to:

  • SR-6316 False-Positive Switch Exhaustiveness Check results in Bad Access / Bad Instruction at Runtime

Issue Description:

Here is the reduced example associated with changes from PR #12818

enum A : Equatable {
  indirect case a([A], foo: Bool)
  indirect case b(Dictionary<String, Int>)
  indirect case c(A, foo: [A])
  indirect case d(if: A, then: A, else: A)
  indirect case e(A, A, foo: Bool)
  indirect case f(A)
  case g(String, foo: Bool)
  case string(String)
  case `nil`

  static func ==(lhs: A, rhs: A) -> Bool { return false }
}

enum B : Equatable {
  static func ==(lhs: B, rhs: B) -> Bool { return false }
}

enum C : Equatable {
  static func ==(lhs: C, rhs: C) -> Bool { return false }
}

enum D : Equatable {
  case a(A)
  case b(B)
  case c(C)
  indirect case d([D])

  static func ==(lhs: D, rhs: D) -> Bool {
    switch (lhs, rhs) {
      case (.a(let r1), .a(let r2)): return r1 == r2
      case (.a, _): return false
      case (.b(let r1), .b(let r2)): return r1 == r2
      case (.b, _): return false
      case (.c(let r1), .c(let r2)): return r1 == r2
      case (.c, _): return false
      case (.d(let r1), .d(let r2)): return r1 == r2
      case (.d, _): return false
    }
  }
}

Which is going to produce "too complex" diagnostic, but compiled fine before the changes.

@xedin
Copy link
Member Author

xedin commented Dec 20, 2017

/cc @rudkx

@CodaFi
Copy link
Member

CodaFi commented Dec 21, 2017

So I thought of a sneaky way around this if we can’t take the source break: We can re-apply the patch and increase the MAX_SPACE limit to compensate for this case.

@AnnaZaks
Copy link
Mannequin

AnnaZaks mannequin commented Jan 17, 2018

We ran into this issue again while testing the swift 5.0 branch. We should revert on master while waiting for the fix!

@CodaFi
Copy link
Member

CodaFi commented Jan 17, 2018

Alright, so to get at the root of this:

The issue that the just-reverted patch was trying to solve is this rather odd-looking switch-case

func largeSpaceMatch(_ x: Bool) {       
 switch (x, (x, x, x, x), (x, x, x, x)) { // expected-error {{switch must be exhaustive}}       
 // expected-note@-1 {{do you want to add a default clause?}}       
 case (true, (_, _, _, _), (_, true, true, _)):     
   break        
 case (true, (_, _, _, _), (_, _, false, _)):       
   break        
 case (_, (_, true, true, _), (_, _, false, _)):        
   break        
 case (_, (_, _, false, _), (_, true, true, _)):        
   break        
 case (_, (_, true, true, _), (_, true, true, _)):      
   break        
 case (_, (_, _, false, _), (_, _, false, _)):      
   break        
 case (_, (false, false, false, false), (_, _, _, _)):      
   break        
 }      
}

The old check believes (correctly) that the Space of the type

(Bool, (Bool, Bool, Bool), (Bool, Bool, Bool))

has a total of 2^7 = 128 inhabitants. The heuristic, however, also believes (incorrectly) that this switch statement covers more than 128 cases - it therefore assumes it covers all possible cases and admits this code to lower phases where it lowers to a fatal error with no debug info. While not technically a miscompile, this is still an open hole in the semantics of switch statements that I would like to close despite source-breakage of a particular form.

The current issue seems to be that projects with machine-generated switch-statements over enums with many associated values (cf the linked AST) are hitting the revised (and stricter) heuristic and getting rejected as potentially non-exhaustive where before they just happened to slip through because of the old incorrect size-based check. Unfortunately, the ways I can think of correcting the old size-based check would also fail to build the switch-statement in this issue or would potentially put the compiler into a flat-loop in the worst case (which is why we came up with these size heuristics in the first place). I would really appreciate the opportunity to roll back to a more correct-but-conservative semantics first and later expand the capabilities of the check to cover complex enums like this when I have a better plan for how to deal with the combinatorial explosion in the checker - even if it means that we have to ask some projects like this one to insert a default clause or twelve in their switch statements.

I will continue to experiment with ways around this, including restoring the size heuristic in cases where we can prove there really is no pattern overlap.

@xedin
Copy link
Member Author

xedin commented Apr 10, 2018

@CodaFi Are you planning to take a look at this issue in the near future?

@CodaFi
Copy link
Member

CodaFi commented Dec 12, 2018

#21230

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
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