`AnyVal` causes segfault
I apologize that I don't have time to minimize this at the moment.
To replicate, check out https://github.com/fthomas/refined/commit/3916b07c1e6934a2c8fe83d75dc10e36d4165d28 and run catsNative/test.
Related? https://github.com/scala-native/scala-native/issues/2597
Isolated, but not minimized. Must be run with scala-cli test bug.scala.
//> using scala "2.13.8"
//> using platform "native"
//> using lib "org.typelevel::cats-kernel-laws::2.8.0"
//> using lib "eu.timepit::refined-scalacheck::0.10.1"
import cats.kernel._
import cats.kernel.laws.discipline._
import eu.timepit.refined._
import eu.timepit.refined.api._
import eu.timepit.refined.scalacheck.numeric._
import eu.timepit.refined.types.numeric._
import eu.timepit.refined.numeric.Positive
import org.scalacheck._
class BugSpec extends Properties("bugged") {
implicit val posByteCommutativeSemigroup: CommutativeSemigroup[PosByte] =
getPosIntegralCommutativeSemigroup[Byte]
private def getPosIntegralCommutativeSemigroup[A: CommutativeSemigroup: NonNegShift](implicit
integral: Integral[A],
v: Validate[A, Positive]
): CommutativeSemigroup[A Refined Positive] =
CommutativeSemigroup.instance { (x, y) =>
val combined: A = Semigroup[A].combine(x.value, y.value)
refineV[Positive](combined).getOrElse {
val result: A =
CommutativeSemigroup[A].combine(NonNegShift[A].shift(combined), integral.one)
refineV[Positive].unsafeFrom(result)
}
}
implicit def eq: Eq[PosByte] = Eq.by(_.value)
property("propped") = CommutativeSemigroupTests[PosByte].commutativeSemigroup.props(0)._2
}
/**
* Typeclass to shift Negative values to Non Negative values.
*/
trait NonNegShift[T] extends Serializable {
def shift(t: T): T
}
object NonNegShift {
def apply[T](implicit ev: NonNegShift[T]): NonNegShift[T] = ev
def instance[T](function: T => T): NonNegShift[T] =
new NonNegShift[T] {
def shift(t: T): T = function(t)
}
// Instances
implicit val byteNonNegShift: NonNegShift[Byte] = instance(t => (t & Byte.MaxValue).toByte)
implicit val shortNonNegShift: NonNegShift[Short] = instance(t => (t & Short.MaxValue).toShort)
implicit val intNonNegShift: NonNegShift[Int] = instance(t => t & Int.MaxValue)
implicit val longNonNegShift: NonNegShift[Long] = instance(t => t & Long.MaxValue)
}
Minimized to no external dependencies. Now it just segfaults.
//> using scala "2.13.8"
//> using platform "native"
trait Eq[@specialized(Byte) A] {
def eqv(x: A, y: A): Boolean
}
object Eq {
def by[A, B](f: A => B)(implicit eqB: Eq[B]): Eq[A] = (x, y) => eqB.eqv(f(x), f(y))
implicit def forByte: Eq[Byte] = _ == _
}
trait CommutativeSemigroup[@specialized(Byte) A] {
def combine(x: A, y: A): A
}
object CommutativeSemigroup {
implicit def forByte: CommutativeSemigroup[Byte] = (x, y) => (x + y).toByte
}
final class Refined[T, P] private (val value: T) extends AnyVal with Serializable {
override def toString: String =
value.toString
}
object Refined {
def unsafeApply[T, P](t: T): Refined[T, P] =
new Refined(t)
}
object Bug {
type Positive
implicit val posByteCommutativeSemigroup: CommutativeSemigroup[Byte Refined Positive] =
getPosIntegralCommutativeSemigroup[Byte]
private def getPosIntegralCommutativeSemigroup[A: CommutativeSemigroup: NonNegShift](implicit
integral: Integral[A],
): CommutativeSemigroup[A Refined Positive] = { (x, y) =>
val combined: A = implicitly[CommutativeSemigroup[A]].combine(x.value, y.value)
Refined.unsafeApply(implicitly[CommutativeSemigroup[A]].combine(implicitly[NonNegShift[A]].shift(combined), integral.one))
}
implicit def eq: Eq[Byte Refined Positive] = Eq.by(_.value)
def main(args: Array[String]): Unit = {
val x = Refined.unsafeApply[Byte, Positive](126.toByte)
val y = Refined.unsafeApply[Byte, Positive](62.toByte)
println(implicitly[Eq[Byte Refined Positive]].eqv(implicitly[CommutativeSemigroup[Byte Refined Positive]].combine(x, y), implicitly[CommutativeSemigroup[Byte Refined Positive]].combine(y, x)))
}
}
/**
* Typeclass to shift Negative values to Non Negative values.
*/
trait NonNegShift[T] {
def shift(t: T): T
}
object NonNegShift {
implicit val byteNonNegShift: NonNegShift[Byte] = t => (t & Byte.MaxValue).toByte
}
Ok, I think this is as far as I can get it.
//> using scala "2.13.8"
//> using platform "native"
// removing the AnyVal makes the problem go away
final class Refined[A] (val value: A) extends AnyVal
object Bug {
def f[A]: Refined[A] => Refined[A] =
x => new Refined(x.value)
def g: Refined[Byte] => Boolean =
x => (x.value == x.value)
def main(args: Array[String]): Unit = {
val x = new Refined[Byte](126.toByte)
println(g.apply(f[Byte].apply(x)))
}
}
$ scala-cli bug.scala
Segmentation fault (core dumped)
Thank you for reporting this Issue. Sorry you encountered it.
This looks like a problem at the Scala level but it helps to know the hardware architecture, OS, and JVM involved. Quirks abound (& rule!).
Am I correct in my memory from Discord that this Issue does not appear with Scala 3.1.1?
Others are more qualified to address the presenting issue. In the background, I am on the hunt for quirks in macOS unit-tests both my personal experience & Issue #2701
Yes, that's right, this issue does not appear in Scala 3. I suspect it is because Scala 3 does not support function specialization, which may be interacting poorly with AnyVal in this particular instance.
$ uname -a
Linux 5.15.0-40-generic #43-Ubuntu SMP Wed Jun 15 12:54:21 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
With address sanitizer you get a little extra info:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1656==ERROR: AddressSanitizer: SEGV on unknown address 0x00000000007e (pc 0x00010278da24 bp 0x0001027dbabc sp 0x00016d6cf2b0 T0)
==1656==The signal is caused by a UNKNOWN memory access.
==1656==Hint: address points to the zero page.
#0 0x10278da24 in _SM27scala.runtime.BoxesRunTime$D11unboxToByteL16java.lang.ObjectbEO+0x30 (arman-scala-native-bug-out:arm64+0x10005da24)
==1656==Register values:
x[0] = 0x0000000000000000 x[1] = 0x000000000000007e x[2] = 0x0000000000000000 x[3] = 0x0000000280000a00
x[4] = 0x0000000280000140 x[5] = 0x0000000000000000 x[6] = 0x00000001027498c0 x[7] = 0x0000000000000000
x[8] = 0x000000000000007e x[9] = 0x000000000000007e x[10] = 0x0000000000000001 x[11] = 0x00000000000000fe
x[12] = 0x0000000000000000 x[13] = 0x0000000000000020 x[14] = 0x0000000000000020 x[15] = 0x0000000073647e85
x[16] = 0x000000018d0b2e00 x[17] = 0x00000001030845e8 x[18] = 0x0000000000000000 x[19] = 0x00000001028fc060
x[20] = 0x000000010278e4c4 x[21] = 0x0000000102c94070 x[22] = 0x0000000000000000 x[23] = 0x0000000000000000
x[24] = 0x0000000000000000 x[25] = 0x0000000000000000 x[26] = 0x0000000000000000 x[27] = 0x0000000000000000
x[28] = 0x0000000000000000 fp = 0x000000016d6cf5c0 lr = 0x00000001027dbabc sp = 0x000000016d6cf2b0
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (arman-scala-native-bug-out:arm64+0x10005da24) in _SM27scala.runtime.BoxesRunTime$D11unboxToByteL16java.lang.ObjectbEO+0x30
_SM27scala.runtime.BoxesRunTime$D11unboxToByteL16java.lang.ObjectbEO
is this: https://github.com/scala-native/scala-native/blob/main/auxlib/src/main/scala/scala/runtime/BoxesRunTime.scala#L30-L31
Which is nothing special, just probably shows the address is wrong.
I hope this is where the problem happens..
With AnyVal
def @"M4Bug$D12$anonfun$g$1L14java.lang.BytezEPT4Bug$" : (@"T4Bug$", @"T14java.lang.Byte") => bool {
%3(%1 : @"T4Bug$", %2 : @"T14java.lang.Byte"):
%4 = unbox[@"T14java.lang.Byte"] %2 : @"T14java.lang.Byte"
%5 = sext[int] %4 : byte
%6 = unbox[@"T14java.lang.Byte"] %2 : @"T14java.lang.Byte"
%7 = sext[int] %6 : byte
%8 = ieq[int] %5 : int, %7 : int
ret %8 : bool
}
Without AnyVal
def @"M4Bug$D12$anonfun$g$1L7RefinedzEPT4Bug$" : (@"T4Bug$", @"T7Refined") => bool {
%3(%1 : @"T4Bug$", %2 : @"T7Refined"):
%4 = method %2 : @"T7Refined", "D5valueL16java.lang.ObjectEO"
%5 = call[(@"T7Refined") => @"T16java.lang.Object"] %4 : ptr(%2 : @"T7Refined")
%6 = unbox[@"T14java.lang.Byte"] %5 : @"T16java.lang.Object"
%7 = sext[int] %6 : byte
%8 = method %2 : @"T7Refined", "D5valueL16java.lang.ObjectEO"
%9 = call[(@"T7Refined") => @"T16java.lang.Object"] %8 : ptr(%2 : @"T7Refined")
%10 = unbox[@"T14java.lang.Byte"] %9 : @"T16java.lang.Object"
%11 = sext[int] %10 : byte
%12 = ieq[int] %7 : int, %11 : int
ret %12 : bool
}