From 0f088022aac31f8ce9f174490c45f481db2faae9 Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Thu, 28 Jan 2016 18:49:47 +0100 Subject: SI-9572 Check for illegal tuple sizes in the parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds explicit checks with syntax errors for tuple literals and types of more than 22 elements. An alternative approach to fixing SI-9572 would be to revert to the old failure mode of Scala 2.10 where references to arbitrary `scala.TupleXY` would be generated in the parser, which then leads to “type/object not found” errors in the typechecker. This fix here is more intrusive but arguably provides a better user experience. Methods `stripParens` and `makeBinop` are moved from `TreeBuilder` to `Parsers` because they can now generate syntax errors. New methods `makeSafeTupleType` and `makeSafeTupleTerm` implement the error checking on top of `makeTupleType` and `makeTupleTerm`. They are overridden with no-op versions in the quasiquotes parser because it also overrides `makeTupleType` and `makeTupleTerm` in a way that supports arbitrary tuple sizes. --- .../scala/reflect/quasiquotes/Parsers.scala | 4 ++ .../scala/tools/nsc/ast/parser/Parsers.scala | 57 ++++++++++++++++++++-- .../scala/tools/nsc/ast/parser/TreeBuilder.scala | 32 ------------ test/files/neg/t9572.check | 7 +++ test/files/neg/t9572.scala | 6 +++ 5 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 test/files/neg/t9572.check create mode 100644 test/files/neg/t9572.scala diff --git a/src/compiler/scala/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/reflect/quasiquotes/Parsers.scala index 97ec7dbfc3..108ad0bc2e 100644 --- a/src/compiler/scala/reflect/quasiquotes/Parsers.scala +++ b/src/compiler/scala/reflect/quasiquotes/Parsers.scala @@ -59,6 +59,10 @@ trait Parsers { self: Quasiquotes => override implicit lazy val fresh: FreshNameCreator = new FreshNameCreator(nme.QUASIQUOTE_PREFIX) + // Do not check for tuple arity. The placeholders can support arbitrary tuple sizes. + override def makeSafeTupleTerm(trees: List[Tree], offset: Offset): Tree = treeBuilder.makeTupleTerm(trees) + override def makeSafeTupleType(trees: List[Tree], offset: Offset): Tree = treeBuilder.makeTupleType(trees) + override val treeBuilder = new ParserTreeBuilder { override implicit def fresh: FreshNameCreator = parser.fresh diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 4494a8ac8d..c04d305f9e 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -766,7 +766,58 @@ self => @inline final def caseSeparated[T](part: => T): List[T] = tokenSeparated(CASE, sepFirst = true, part) def readAnnots(part: => Tree): List[Tree] = tokenSeparated(AT, sepFirst = true, part) -/* --------- OPERAND/OPERATOR STACK --------------------------------------- */ + /** Create a tuple type Tree. If the arity is not supported, a syntax error is emitted. */ + def makeSafeTupleType(elems: List[Tree], offset: Offset) = { + if (checkTupleSize(elems, offset)) makeTupleType(elems) + else makeTupleType(Nil) // create a dummy node; makeTupleType(elems) would fail + } + + /** Create a tuple term Tree. If the arity is not supported, a syntax error is emitted. */ + def makeSafeTupleTerm(elems: List[Tree], offset: Offset) = { + checkTupleSize(elems, offset) + makeTupleTerm(elems) + } + + private[this] def checkTupleSize(elems: List[Tree], offset: Offset): Boolean = + if (elems.lengthCompare(definitions.MaxTupleArity) > 0) { + syntaxError(offset, "too many elements for tuple: "+elems.length+", allowed: "+definitions.MaxTupleArity, skipIt = false) + false + } else true + + /** Strip the artifitial `Parens` node to create a tuple term Tree. */ + def stripParens(t: Tree) = t match { + case Parens(ts) => atPos(t.pos) { makeSafeTupleTerm(ts, t.pos.point) } + case _ => t + } + + /** Create tree representing (unencoded) binary operation expression or pattern. */ + def makeBinop(isExpr: Boolean, left: Tree, op: TermName, right: Tree, opPos: Position, targs: List[Tree] = Nil): Tree = { + require(isExpr || targs.isEmpty || targs.exists(_.isErroneous), s"Incompatible args to makeBinop: !isExpr but targs=$targs") + + def mkSelection(t: Tree) = { + def sel = atPos(opPos union t.pos)(Select(stripParens(t), op.encode)) + if (targs.isEmpty) sel else atPos(left.pos)(TypeApply(sel, targs)) + } + def mkNamed(args: List[Tree]) = if (isExpr) args map treeInfo.assignmentToMaybeNamedArg else args + val arguments = right match { + case Parens(args) => mkNamed(args) + case _ => List(right) + } + if (isExpr) { + if (treeInfo.isLeftAssoc(op)) { + Apply(mkSelection(left), arguments) + } else { + val x = freshTermName() + Block( + List(ValDef(Modifiers(symtab.Flags.SYNTHETIC | symtab.Flags.ARTIFACT), x, TypeTree(), stripParens(left))), + Apply(mkSelection(right), List(Ident(x)))) + } + } else { + Apply(Ident(op.encode), stripParens(left) :: arguments) + } + } + + /* --------- OPERAND/OPERATOR STACK --------------------------------------- */ /** Modes for infix types. */ object InfixMode extends Enumeration { @@ -870,7 +921,7 @@ self => atPos(start, in.skipToken()) { makeFunctionTypeTree(ts, typ()) } else { ts foreach checkNotByNameOrVarargs - val tuple = atPos(start) { makeTupleType(ts) } + val tuple = atPos(start) { makeSafeTupleType(ts, start) } infixTypeRest( compoundTypeRest( annotTypeRest( @@ -937,7 +988,7 @@ self => def simpleType(): Tree = { val start = in.offset simpleTypeRest(in.token match { - case LPAREN => atPos(start)(makeTupleType(inParens(types()))) + case LPAREN => atPos(start)(makeSafeTupleType(inParens(types()), start)) case USCORE => wildcardType(in.skipToken()) case _ => path(thisOK = false, typeOK = true) match { diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 6e5a3f6ef7..cc9e39f430 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -43,44 +43,12 @@ abstract class TreeBuilder { def makeTupleType(elems: List[Tree]) = gen.mkTupleType(elems) - def stripParens(t: Tree) = t match { - case Parens(ts) => atPos(t.pos) { makeTupleTerm(ts) } - case _ => t - } - def makeAnnotated(t: Tree, annot: Tree): Tree = atPos(annot.pos union t.pos)(Annotated(annot, t)) def makeSelfDef(name: TermName, tpt: Tree): ValDef = ValDef(Modifiers(PRIVATE), name, tpt, EmptyTree) - /** Create tree representing (unencoded) binary operation expression or pattern. */ - def makeBinop(isExpr: Boolean, left: Tree, op: TermName, right: Tree, opPos: Position, targs: List[Tree] = Nil): Tree = { - require(isExpr || targs.isEmpty || targs.exists(_.isErroneous), s"Incompatible args to makeBinop: !isExpr but targs=$targs") - - def mkSelection(t: Tree) = { - def sel = atPos(opPos union t.pos)(Select(stripParens(t), op.encode)) - if (targs.isEmpty) sel else atPos(left.pos)(TypeApply(sel, targs)) - } - def mkNamed(args: List[Tree]) = if (isExpr) args map treeInfo.assignmentToMaybeNamedArg else args - val arguments = right match { - case Parens(args) => mkNamed(args) - case _ => List(right) - } - if (isExpr) { - if (treeInfo.isLeftAssoc(op)) { - Apply(mkSelection(left), arguments) - } else { - val x = freshTermName() - Block( - List(ValDef(Modifiers(SYNTHETIC | ARTIFACT), x, TypeTree(), stripParens(left))), - Apply(mkSelection(right), List(Ident(x)))) - } - } else { - Apply(Ident(op.encode), stripParens(left) :: arguments) - } - } - /** Tree for `od op`, start is start0 if od.pos is borked. */ def makePostfixSelect(start0: Int, end: Int, od: Tree, op: Name): Tree = { val start = if (od.pos.isDefined) od.pos.start else start0 diff --git a/test/files/neg/t9572.check b/test/files/neg/t9572.check new file mode 100644 index 0000000000..b95bd015cf --- /dev/null +++ b/test/files/neg/t9572.check @@ -0,0 +1,7 @@ +t9572.scala:3: error: too many elements for tuple: 23, allowed: 22 + val term23 = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23) + ^ +t9572.scala:5: error: too many elements for tuple: 23, allowed: 22 + val type23: (Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int) = null + ^ +two errors found diff --git a/test/files/neg/t9572.scala b/test/files/neg/t9572.scala new file mode 100644 index 0000000000..32b2db320e --- /dev/null +++ b/test/files/neg/t9572.scala @@ -0,0 +1,6 @@ +class T9572 { + val term22 = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22) + val term23 = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23) + val type22: (Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int) = null + val type23: (Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int) = null +} -- cgit v1.2.3