From 967db68f72de28d17c27a543f61d5041ecff5549 Mon Sep 17 00:00:00 2001 From: Bernd Louis Date: Thu, 7 Jan 2016 18:20:41 +0100 Subject: [PATCH] [MACROS] Issue 84: Semantic checks for unstable applications in enclosing scope (includes writes to outer mutables and unstable function / method application) --- .../eu/stratosphere/emma/api/package.scala | 6 ++ .../emma/macros/program/SemanticChecks.scala | 22 +++++- .../emma/macros/SemanticChecksTest.scala | 76 ++++++++++++++++++- 3 files changed, 100 insertions(+), 4 deletions(-) diff --git a/emma-common/src/main/scala/eu/stratosphere/emma/api/package.scala b/emma-common/src/main/scala/eu/stratosphere/emma/api/package.scala index 9b4088966..be787797d 100644 --- a/emma-common/src/main/scala/eu/stratosphere/emma/api/package.scala +++ b/emma-common/src/main/scala/eu/stratosphere/emma/api/package.scala @@ -168,4 +168,10 @@ package object api { | "updateWith* call, and use that one in place of this .bag() call.""".stripMargin) // Note: Don't catch this! // This should terminate the program, because the stateful will be in an invalid state after throwing this. + + class UnstableApplicationToEnclosingScopeException(id: String) extends InvalidProgramException( s""" + | The user defined function is performing an application in the enclosing scope: (`$id`). + | This may also be the result of write access to a mutable member of the enclosing scope + | (for instance setting a mutable variable or attribute). + """.stripMargin) } \ No newline at end of file diff --git a/emma-language/src/main/scala/eu/stratosphere/emma/macros/program/SemanticChecks.scala b/emma-language/src/main/scala/eu/stratosphere/emma/macros/program/SemanticChecks.scala index 49c183daf..3d1ed169a 100644 --- a/emma-language/src/main/scala/eu/stratosphere/emma/macros/program/SemanticChecks.scala +++ b/emma-language/src/main/scala/eu/stratosphere/emma/macros/program/SemanticChecks.scala @@ -1,14 +1,17 @@ package eu.stratosphere.emma.macros.program -import eu.stratosphere.emma.api.StatefulAccessedFromUdfException +import eu.stratosphere.emma.api.{UnstableApplicationToEnclosingScopeException, StatefulAccessedFromUdfException} import eu.stratosphere.emma.macros.program.comprehension.ComprehensionAnalysis private[emma] trait SemanticChecks extends ComprehensionAnalysis { import universe._ import syntax._ - val doSemanticChecks: Tree => Unit = - checkForStatefulAccessedFromUdf + val doSemanticChecks: Tree => Unit = { t => + checkForStatefulAccessedFromUdf(t) + checkForUnstableApplicationsInEnclosingScope(t) + } + /** * Checks that `.bag()` is not called from the UDF of an `updateWith*` on the same stateful that @@ -21,4 +24,17 @@ private[emma] trait SemanticChecks extends ComprehensionAnalysis { if (UDFs exists { _.closure(id.term) }) throw new StatefulAccessedFromUdfException() } + + /** + * Prohibits applications to functions / methods in the enclosing object / closure. + * This includes write references to mutable variables or properties (will + * become applications after type checking due to UAP). + * + * @param tree + */ + def checkForUnstableApplicationsInEnclosingScope(tree: Tree) = traverse(tree) { + case a @ Apply(Select(This(TypeName(_)), TermName(id)),_) + if !a.symbol.asTerm.isStable => + throw new UnstableApplicationToEnclosingScopeException(id) + } } diff --git a/emma-language/src/test/scala/eu/stratosphere/emma/macros/SemanticChecksTest.scala b/emma-language/src/test/scala/eu/stratosphere/emma/macros/SemanticChecksTest.scala index 7adabbdc3..3f97aec78 100644 --- a/emma-language/src/test/scala/eu/stratosphere/emma/macros/SemanticChecksTest.scala +++ b/emma-language/src/test/scala/eu/stratosphere/emma/macros/SemanticChecksTest.scala @@ -93,10 +93,84 @@ class SemanticChecksTest extends FlatSpec with Matchers with RuntimeUtil { } } + it should "check write access and application of outer methods / functions / variables / properties" in { + type E = UnstableApplicationToEnclosingScopeException + q""" + object SomeObject { + var x = 0 + emma.parallelize { + List(23,42,99).foreach { y => x = x + y } + } + } + """ should failWith[E] + + q""" + object SomeObject { + var x = 0 + emma.parallelize { + List(23,42,99).foreach { y => x += y } + } + } + """ should failWith[E] + + q""" + object SomeObject { + var x = 0 + emma.parallelize { + x += 1 + } + } + """ should failWith[E] + + q""" + object SomeClosure { + var x = 0 + emma.parallelize { + val lst = List(23,99,42) // disambiguation default arguments / Loc + x = lst.foldLeft(Int.MinValue)(Math.max(_,_)) + } + } + """ should failWith[E] + + q""" + class MyClass(var x:Int = 0) { + emma.parallelize { + val lst = List(23,99,42) + x = lst.foldLeft(Int.MinValue)(Math.max(_,_)) + } + } + """ should failWith[E] + + q""" + class MyClass { + private var _x:Int = 42 + def x = _x + def x_=(newX:Int) { _x = newX} + + emma.parallelize { + val lst = List(23,99,42) + x = lst.foldLeft(Int.MinValue)(Math.max(_,_)) + } + } + """ should failWith[E] + + noException should be thrownBy {tb.typecheck(withImports( + q""" + object MyObject { + def getFourtyTwo:Int = 42 + emma.parallelize { + List(1,2,3,getFourtyTwo, 99).map(_ * 2) + } + } + """))} + } + def failWith[E <: Throwable : ClassTag] = include (implicitly[ClassTag[E]].runtimeClass.getSimpleName) compose { (tree: Tree) => - intercept[ToolBoxError] { tb.typecheck(q"{ ..$imports; $tree }") }.getMessage + intercept[ToolBoxError] { tb.typecheck(withImports(tree)) }.getMessage } + + def withImports(tree: universe.Tree): universe.Tree = q"{ ..$imports; $tree }" } object SemanticChecksTest {