Skip to content

Commit

Permalink
Fix calls to java methods with type bounds.
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasstucki committed Dec 23, 2016
1 parent 0cae07d commit 5d8a71c
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 49 deletions.
Expand Up @@ -502,7 +502,11 @@ class CallGraphBuilder(collectedSummaries: Map[Symbol, MethodSummary], mode: Int
summary.accessedModules.foreach(x => addReachableType(new TypeWithContext(x.info, parentRefinements(x.info)), method))
summary.definedClosures.foreach(x => addReachableClosure(x, method))
summary.methodsCalled.foreach {
case (receiver, theseCallSites) => theseCallSites.foreach(callSite => processCallSite(callSite, instantiatedTypes, method, receiver))
case (receiver, theseCallSites) => theseCallSites.foreach { callSite =>
val nw = instantiateCallSite(method, receiver, callSite, instantiatedTypes)
reachableMethods ++= nw
method.addOutEdges(callSite, nw)
}
}

case None =>
Expand Down Expand Up @@ -534,65 +538,71 @@ class CallGraphBuilder(collectedSummaries: Map[Symbol, MethodSummary], mode: Int
}

private def processCallsFromJava(instantiatedTypes: immutable.Set[TypeWithContext], method: CallInfoWithContext): Unit = {
def allNonJavaDecls(argType: Type) = {
(argType.decls ++ argType.parents.filter(!_.symbol.is(JavaDefined)).flatMap(_.decls)).toSet
}
def allDecls(argType: Type) =
(argType.decls ++ argType.parents.flatMap(_.decls)).toSet

val addedTypes = mutable.HashSet.empty[Type]
def isJavaClass(sym: Symbol) =
sym == defn.AnyClass || sym == defn.ObjectClass || sym.is(JavaDefined)

def addAllPotentialCallsFor(argType: Type): Unit = {
if (!defn.isPrimitiveClass(argType.classSymbol) && !addedTypes.contains(argType)) {
addedTypes.add(argType)
for {
decl <- allNonJavaDecls(argType)
if decl.isTerm && !decl.isConstructor
if decl.name != nme.isInstanceOf_ && decl.name != nme.asInstanceOf_ && decl.name != nme.synchronized_
} {
val termName = decl.name.asTermName
val paramTypes = decl.info.paramTypess.flatten

def addCall(call: TermRef): Unit = {
val targs = call.widenDealias match {
case call: PolyType => call.paramBounds.map(_.hi)
case _ => Nil
}
processCallSite(CallInfo(call, targs, paramTypes), instantiatedTypes, method, argType)
// TODO add transitively reachable calls from java (fix link-code-from-java-3.scala)
// val resultType = call.widenDealias.finalResultType.widenDealias
// addAllPotentialCallsFor(resultType)
def allPotentialCallsFor(argType: Type): Set[CallInfo] = {
if (defn.isPrimitiveClass(argType.classSymbol) || isJavaClass(argType.widenDealias.classSymbol)) {
Set.empty
} else {
def potentialCall(decl: Symbol) = {
def paramTypes = decl.info.paramTypess.flatten
val call = new TermRefWithFixedSym(argType, decl.name.asTermName, decl.asTerm)
val targs = call.widenDealias match {
case call: PolyType =>
def erasedBounds(tp: TypeBounds): Type = tp.hi match {
case RefinedType(parent, refinedName, refinedInfo: TypeBounds) =>
RefinedType(parent, refinedName, erasedBounds(refinedInfo))
case t => t
}
call.paramBounds.map(erasedBounds)

case _ => Nil
}

val definedInJavaClass: Boolean = {
def isDefinedInJavaClass(sym: Symbol) =
sym.owner == defn.AnyClass || sym.owner.is(JavaDefined)
def isDefinedInJavaClass(sym: Symbol) =
sym.owner == defn.AnyClass || sym.owner.is(JavaDefined)

val definedInJavaClass: Boolean =
isDefinedInJavaClass(decl) || decl.allOverriddenSymbols.exists(isDefinedInJavaClass)
}

argType match {
case argType: PreciseType =>
if (definedInJavaClass)
addCall(new TermRefWithFixedSym(argType, termName, decl.asTerm))
if (!definedInJavaClass) Nil
else List(CallInfo(call, targs, paramTypes))

case _ =>
// FIXME
// val argTypeWiden = argType.widenDealias
// if (argTypeWiden.member(termName).exists) {
// val sym = argTypeWiden.classSymbol.requiredMethod(termName, paramTypes)
// if (!definedInJavaClass || !sym.isEffectivelyFinal)
// addCall(TermRef(argType, sym))
// }
val argTypeWiden = argType.widenDealias
lazy val sym = argTypeWiden.classSymbol.requiredMethod(decl.name, paramTypes)
if (!argTypeWiden.member(decl.name).exists || !definedInJavaClass || (sym.isEffectivelyFinal && isDefinedInJavaClass(decl))) Nil
else List(CallInfo(TermRef(argType, sym), targs, paramTypes))

}
}

for {
decl <- allDecls(argType)
if decl.isTerm && !decl.isConstructor && decl.name != nme.COMPANION_MODULE_METHOD
if decl.name != nme.isInstanceOf_ && decl.name != nme.asInstanceOf_ && decl.name != nme.synchronized_
callInfo <- potentialCall(decl)
} yield callInfo
}
}

method.argumentsPassed.foreach(addAllPotentialCallsFor)
}

private def processCallSite(callSite: CallInfo, instantiatedTypes: immutable.Set[TypeWithContext], method: CallInfoWithContext, receiver: Type): Unit = {
val nw = instantiateCallSite(method, receiver, callSite, instantiatedTypes)
reachableMethods ++= nw
method.addOutEdges(callSite, nw)
// FIXME java method could potentially call this.xyz()
// addAllPotentialCallsFor(method.call.normalizedPrefix)
for {
rec <- method.argumentsPassed.distinct
potentialCall <- allPotentialCallsFor(rec)
if method.getOutEdges(potentialCall).isEmpty
} {
val nw = instantiateCallSite(method, rec, potentialCall, instantiatedTypes)
reachableMethods ++= nw
method.addOutEdges(potentialCall, nw)
}
}

}
1 change: 1 addition & 0 deletions tests/link-dce-failing/link-code-from-java-7.check
@@ -0,0 +1 @@
42
21 changes: 21 additions & 0 deletions tests/link-dce-failing/link-code-from-java-7.scala
@@ -0,0 +1,21 @@

import java.lang.InheritableThreadLocal

import scala.annotation.internal


object Test {
// @internal.link.CallGraphBounds(reachableClasses = 20, classesWithReachableMethods = 6, reachableMethods = 7)
def main(args: Array[String]): Unit = {
System.out.println(new DynamicVariableFoo[String]("42").value)
}
}


class DynamicVariableFoo[T](init: T) {
private val tl = new InheritableThreadLocal[T] {
override def initialValue = init.asInstanceOf[T with AnyRef]
}

def value: T = tl.get.asInstanceOf[T]
}
2 changes: 1 addition & 1 deletion tests/link-dce/link-code-from-java-5.scala
Expand Up @@ -2,7 +2,7 @@ import scala.annotation.internal
import scala.math.Ordering

object Test {
@internal.link.CallGraphBounds(reachableClasses = 151, classesWithReachableMethods = 19, reachableMethods = 82)
@internal.link.CallGraphBounds(reachableClasses = 152, classesWithReachableMethods = 21, reachableMethods = 90)
def main(args: Array[String]): Unit = {
val arr = new Array[Object](1)
arr(0) = 42
Expand Down
2 changes: 1 addition & 1 deletion tests/link-dce/link-innerClass-1.scala
@@ -1,7 +1,7 @@
import scala.annotation.internal

object Test {
@internal.link.CallGraphBounds(reachableClasses = 21, classesWithReachableMethods = 8, reachableMethods = 11)
@internal.link.CallGraphBounds(reachableClasses = 22, classesWithReachableMethods = 8, reachableMethods = 11)
def main(args: Array[String]): Unit = {
new Bar().test()
}
Expand Down
2 changes: 1 addition & 1 deletion tests/link-dce/link-mixinInit-2.scala
@@ -1,7 +1,7 @@
import scala.annotation.internal

object Test {
@internal.link.CallGraphBounds(reachableClasses = 21, classesWithReachableMethods = 8, reachableMethods = 10)
@internal.link.CallGraphBounds(reachableClasses = 22, classesWithReachableMethods = 8, reachableMethods = 10)
def main(args: Array[String]): Unit = {
new Bar
}
Expand Down
2 changes: 1 addition & 1 deletion tests/link-dce/link-secondary-constructor-0.scala
@@ -1,7 +1,7 @@
import scala.annotation.internal

object Test {
@internal.link.CallGraphBounds(reachableClasses = 20, classesWithReachableMethods = 7, reachableMethods = 11)
@internal.link.CallGraphBounds(reachableClasses = 21, classesWithReachableMethods = 7, reachableMethods = 11)
def main(args: Array[String]): Unit = {
new Foo()
}
Expand Down

0 comments on commit 5d8a71c

Please sign in to comment.