Skip to content

Commit b53bf1e

Browse files
committed
Harden Tuple operations against wrong inputs
1 parent 2d83885 commit b53bf1e

File tree

6 files changed

+115
-98
lines changed

6 files changed

+115
-98
lines changed

library/src-scala3/scala/Tuple.scala

Lines changed: 99 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -5,82 +5,88 @@ import typelevel._
55
sealed trait Tuple extends Any {
66
import Tuple._
77

8-
rewrite def toArray: Array[Object] = rewrite constValue[BoundedSize[this.type]] match {
9-
case 0 =>
8+
rewrite def toArray: Array[Object] = rewrite constValueOpt[BoundedSize[this.type]] match {
9+
case Some(0) =>
1010
$emptyArray
11-
case 1 =>
11+
case Some(1) =>
1212
val t = asInstanceOf[Tuple1[Object]]
1313
Array(t._1)
14-
case 2 =>
14+
case Some(2) =>
1515
val t = asInstanceOf[Tuple2[Object, Object]]
1616
Array(t._1, t._2)
17-
case 3 =>
17+
case Some(3) =>
1818
val t = asInstanceOf[Tuple3[Object, Object, Object]]
1919
Array(t._1, t._2, t._3)
20-
case 4 =>
20+
case Some(4) =>
2121
val t = asInstanceOf[Tuple4[Object, Object, Object, Object]]
2222
Array(t._1, t._2, t._3, t._4)
23-
case n if n <= $MaxSpecialized =>
23+
case Some(n) if n <= $MaxSpecialized =>
2424
$toArray(this, n)
25-
case n =>
25+
case Some(n) =>
2626
asInstanceOf[TupleXXL].elems
27+
case None =>
28+
error(".toArray cannot be applied to tuple of unknown size")
2729
}
2830

29-
rewrite def *: [H] (x: H): Tuple = {
30-
erased val resTpe = Typed(_pair(x, this))
31-
rewrite _size(this) match {
32-
case 0 =>
33-
Tuple1(x).asInstanceOf[resTpe.Type]
34-
case 1 =>
35-
Tuple2(x, asInstanceOf[Tuple1[_]]._1).asInstanceOf[resTpe.Type]
36-
case 2 =>
31+
rewrite def *: [H] (x: H): H *: this.type = {
32+
type Result = H *: this.type
33+
rewrite constValueOpt[BoundedSize[this.type]] match {
34+
case Some(0) =>
35+
Tuple1(x).asInstanceOf[Result]
36+
case Some(1) =>
37+
Tuple2(x, asInstanceOf[Tuple1[_]]._1).asInstanceOf[Result]
38+
case Some(2) =>
3739
val t = asInstanceOf[Tuple2[_, _]]
38-
Tuple3(x, t._1, t._2).asInstanceOf[resTpe.Type]
39-
case 3 =>
40+
Tuple3(x, t._1, t._2).asInstanceOf[Result]
41+
case Some(3) =>
4042
val t = asInstanceOf[Tuple3[_, _, _]]
41-
Tuple4(x, t._1, t._2, t._3).asInstanceOf[resTpe.Type]
42-
case 4 =>
43+
Tuple4(x, t._1, t._2, t._3).asInstanceOf[Result]
44+
case Some(4) =>
4345
val t = asInstanceOf[Tuple4[_, _, _, _]]
44-
Tuple5(x, t._1, t._2, t._3, t._4).asInstanceOf[resTpe.Type]
45-
case n =>
46-
fromArray[resTpe.Type]($consArray(x, toArray))
46+
Tuple5(x, t._1, t._2, t._3, t._4).asInstanceOf[Result]
47+
case Some(n) =>
48+
fromArray[Result]($consArray(x, toArray))
49+
case _ =>
50+
error("*: cannot be applied to tuple of unknown size")
4751
}
4852
}
4953

50-
rewrite def ++(that: Tuple): Tuple = {
54+
rewrite def ++(that: Tuple): Concat[this.type, that.type] = {
5155
type Result = Concat[this.type, that.type]
52-
rewrite constValue[BoundedSize[this.type]] match {
53-
case 0 =>
54-
that
55-
case 1 =>
56-
if (constValue[BoundedSize[that.type]] == 0) this
56+
rewrite constValueOpt[BoundedSize[this.type]] match {
57+
case Some(0) =>
58+
that.asInstanceOf[Result]
59+
case Some(1) =>
60+
if (constValue[BoundedSize[that.type]] == 0) this.asInstanceOf[Result]
5761
else (asInstanceOf[Tuple1[_]]._1 *: that).asInstanceOf[Result]
58-
case 2 =>
62+
case Some(2) =>
5963
val t = asInstanceOf[Tuple2[_, _]]
6064
rewrite constValue[BoundedSize[that.type]] match {
61-
case 0 => this
65+
case 0 => this.asInstanceOf[Result]
6266
case 1 =>
6367
val u = that.asInstanceOf[Tuple1[_]]
6468
Tuple3(t._1, t._2, u._1).asInstanceOf[Result]
6569
case 2 =>
6670
val u = that.asInstanceOf[Tuple2[_, _]]
6771
Tuple4(t._1, t._2, u._1, u._2).asInstanceOf[Result]
6872
case _ =>
69-
genericConcat[Result](this, that)
73+
genericConcat[Result](this, that).asInstanceOf[Result]
7074
}
71-
case 3 =>
75+
case Some(3) =>
7276
val t = asInstanceOf[Tuple3[_, _, _]]
7377
rewrite constValue[BoundedSize[that.type]] match {
74-
case 0 => this
78+
case 0 => this.asInstanceOf[Result]
7579
case 1 =>
7680
val u = that.asInstanceOf[Tuple1[_]]
7781
Tuple4(t._1, t._2, t._3, u._1).asInstanceOf[Result]
7882
case _ =>
79-
genericConcat[Result](this, that)
83+
genericConcat[Result](this, that).asInstanceOf[Result]
8084
}
81-
case _ =>
82-
if (constValue[BoundedSize[that.type]] == 0) this
83-
else genericConcat[Result](this, that)
85+
case Some(_) =>
86+
if (constValue[BoundedSize[that.type]] == 0) this.asInstanceOf[Result]
87+
else genericConcat[Result](this, that).asInstanceOf[Result]
88+
case None =>
89+
error("++ cannot be applied to tuple of unknown size")
8490
}
8591
}
8692

@@ -92,6 +98,14 @@ object Tuple {
9298
transparent val $MaxSpecialized = 22
9399
transparent private val XXL = $MaxSpecialized + 1
94100

101+
type Head[X <: NonEmptyTuple] = X match {
102+
case x *: _ => x
103+
}
104+
105+
type Tail[X <: NonEmptyTuple] <: Tuple = X match {
106+
case _ *: xs => xs
107+
}
108+
95109
type Concat[X <: Tuple, Y <: Tuple] <: Tuple = X match {
96110
case Unit => Y
97111
case x1 *: xs1 => x1 *: Concat[xs1, Y]
@@ -142,33 +156,6 @@ object Tuple {
142156
elems1
143157
}
144158

145-
private[scala] rewrite def _pair[H, T <: Tuple] (x: H, xs: T): Tuple =
146-
erasedValue[H *: T]
147-
148-
private[scala] rewrite def _size(xs: Tuple): Int =
149-
rewrite xs match {
150-
case _: Unit => 0
151-
case _: (_ *: xs1) => _size(erasedValue[xs1]) + 1
152-
}
153-
154-
private[scala] rewrite def _head(xs: Tuple): Any = rewrite xs match {
155-
case _: (x *: _) => erasedValue[x]
156-
}
157-
158-
private[scala] rewrite def _tail(xs: Tuple): Tuple = rewrite xs match {
159-
case _: (_ *: xs1) => erasedValue[xs1]
160-
}
161-
162-
private[scala] rewrite def _index(xs: Tuple, n: Int): Any = rewrite xs match {
163-
case _: (x *: _) if n == 0 => erasedValue[x]
164-
case _: (_ *: xs1) if n > 0 => _index(erasedValue[xs1], n - 1)
165-
}
166-
167-
private[scala] rewrite def _concat(xs: Tuple, ys: Tuple): Tuple = rewrite xs match {
168-
case _: Unit => ys
169-
case _: (x1 *: xs1) => _pair(erasedValue[x1], _concat(erasedValue[xs1], ys))
170-
}
171-
172159
rewrite def fromArray[T <: Tuple](xs: Array[Object]): T =
173160
rewrite constValue[BoundedSize[T]] match {
174161
case 0 => ().asInstanceOf[T]
@@ -201,85 +188,99 @@ object Tuple {
201188
abstract sealed class NonEmptyTuple extends Tuple {
202189
import Tuple._
203190

204-
rewrite def head: Any = {
205-
erased val resTpe = Typed(_head(this))
206-
val resVal = rewrite _size(this) match {
207-
case 1 =>
191+
rewrite def head: Head[this.type] = {
192+
type Result = Head[this.type]
193+
val resVal = rewrite constValueOpt[BoundedSize[this.type]] match {
194+
case Some(1) =>
208195
val t = asInstanceOf[Tuple1[_]]
209196
t._1
210-
case 2 =>
197+
case Some(2) =>
211198
val t = asInstanceOf[Tuple2[_, _]]
212199
t._1
213-
case 3 =>
200+
case Some(3) =>
214201
val t = asInstanceOf[Tuple3[_, _, _]]
215202
t._1
216-
case 4 =>
203+
case Some(4) =>
217204
val t = asInstanceOf[Tuple4[_, _, _, _]]
218205
t._1
219-
case n if n > 4 && n <= $MaxSpecialized =>
206+
case Some(n) if n > 4 && n <= $MaxSpecialized =>
220207
asInstanceOf[Product].productElement(0)
221-
case n if n > $MaxSpecialized =>
208+
case Some(n) if n > $MaxSpecialized =>
222209
val t = asInstanceOf[TupleXXL]
223210
t.elems(0)
211+
case None =>
212+
error(".head cannot be applied to tuple of unknown size")
224213
}
225-
resVal.asInstanceOf[resTpe.Type]
214+
resVal.asInstanceOf[Result]
226215
}
227216

228-
rewrite def tail: Tuple = {
229-
erased val resTpe = Typed(_tail(this))
230-
rewrite _size(this) match {
231-
case 1 =>
232-
()
233-
case 2 =>
217+
rewrite def tail: Tail[this.type] = {
218+
type Result = Tail[this.type]
219+
rewrite constValueOpt[BoundedSize[this.type]] match {
220+
case Some(1) =>
221+
().asInstanceOf[Result]
222+
case Some(2) =>
234223
val t = asInstanceOf[Tuple2[_, _]]
235-
Tuple1(t._2).asInstanceOf[resTpe.Type]
236-
case 3 =>
224+
Tuple1(t._2).asInstanceOf[Result]
225+
case Some(3) =>
237226
val t = asInstanceOf[Tuple3[_, _, _]]
238-
Tuple2(t._2, t._3).asInstanceOf[resTpe.Type]
239-
case 4 =>
227+
Tuple2(t._2, t._3).asInstanceOf[Result]
228+
case Some(4) =>
240229
val t = asInstanceOf[Tuple4[_, _, _, _]]
241-
Tuple3(t._2, t._3, t._4).asInstanceOf[resTpe.Type]
242-
case 5 =>
230+
Tuple3(t._2, t._3, t._4).asInstanceOf[Result]
231+
case Some(5) =>
243232
val t = asInstanceOf[Tuple5[_, _, _, _, _]]
244-
Tuple4(t._2, t._3, t._4, t._5).asInstanceOf[resTpe.Type]
245-
case n if n > 5 =>
246-
fromArray[resTpe.Type](toArray.tail)
233+
Tuple4(t._2, t._3, t._4, t._5).asInstanceOf[Result]
234+
case Some(n) if n > 5 =>
235+
fromArray[Result](toArray.tail)
236+
case None =>
237+
error(".tail cannot be applied to tuple of unknown size")
247238
}
248239
}
249240

250-
rewrite def apply(n: Int): Any = {
241+
rewrite def indexOutOfBounds = error("index out of bounds")
242+
243+
rewrite def apply(transparent n: Int): Elem[this.type, n.type] = {
251244
type Result = Elem[this.type, n.type]
252-
rewrite constValue[BoundedSize[this.type]] match {
253-
case 1 =>
245+
rewrite constValueOpt[BoundedSize[this.type]] match {
246+
case Some(1) =>
254247
val t = asInstanceOf[Tuple1[_]]
255248
rewrite n match {
256249
case 0 => t._1.asInstanceOf[Result]
250+
case _ => indexOutOfBounds
257251
}
258-
case 2 =>
252+
case Some(2) =>
259253
val t = asInstanceOf[Tuple2[_, _]]
260254
rewrite n match {
261255
case 0 => t._1.asInstanceOf[Result]
262256
case 1 => t._2.asInstanceOf[Result]
257+
case _ => indexOutOfBounds
263258
}
264-
case 3 =>
259+
case Some(3) =>
265260
val t = asInstanceOf[Tuple3[_, _, _]]
266261
rewrite n match {
267262
case 0 => t._1.asInstanceOf[Result]
268263
case 1 => t._2.asInstanceOf[Result]
269264
case 2 => t._3.asInstanceOf[Result]
265+
case _ => indexOutOfBounds
270266
}
271-
case 4 =>
267+
case Some(4) =>
272268
val t = asInstanceOf[Tuple4[_, _, _, _]]
273269
rewrite n match {
274270
case 0 => t._1.asInstanceOf[Result]
275271
case 1 => t._2.asInstanceOf[Result]
276272
case 2 => t._3.asInstanceOf[Result]
277273
case 3 => t._4.asInstanceOf[Result]
274+
case _ => indexOutOfBounds
278275
}
279-
case s if s > 4 && s <= $MaxSpecialized && n >= 0 && n < s =>
276+
case Some(s) if s > 4 && s <= $MaxSpecialized && n >= 0 && n < s =>
280277
asInstanceOf[Product].productElement(n).asInstanceOf[Result]
281-
case s if s > $MaxSpecialized && n >= 0 && n < s =>
278+
case Some(s) if s > $MaxSpecialized && n >= 0 && n < s =>
282279
asInstanceOf[TupleXXL].elems(n).asInstanceOf[Result]
280+
case Some(s) =>
281+
indexOutOfBounds
282+
case None =>
283+
error("selection (...) cannot be applied to tuple of unknown size")
283284
}
284285
}
285286
}

tests/neg/tuple-nonconstant.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Test {
2+
def cons[X, Xs <: Tuple](x: X, xs: Xs) = x *: xs // error: *: cannot be applied to tuple of unknown size
3+
def toArray[Xs <: Tuple](xs: Xs) = xs.toArray // (second error is suppressed right now)
4+
}

tests/neg/tuple-nonconstant2.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Test {
2+
def toArray[Xs <: Tuple](xs: Xs) = xs.toArray // error: toArray cannot be applied to tuple of unknown size
3+
}

tests/neg/tuple-nonconstant3.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Test {
2+
def elem[Xs <: NonEmptyTuple](xs: Xs) = xs(1) // error: selection (...) cannot be applied to tuple of unknown size
3+
}

tests/neg/tuple-oob1.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Test {
2+
def elem(xs: (Int, String)) = xs(2) // error: index out of bounds
3+
}

tests/neg/tuple-oob2.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Test {
2+
def elem(xs: (Int, String), n: Int) = xs(n) // error: argument to transparent parameter must be a constant expression
3+
}

0 commit comments

Comments
 (0)