diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/advanced_counter_examples/PrematurelyReadFinalField.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/advanced_counter_examples/PrematurelyReadFinalField.java new file mode 100644 index 0000000000..b94859d7e5 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/advanced_counter_examples/PrematurelyReadFinalField.java @@ -0,0 +1,35 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.immutability.openworld.assignability.advanced_counter_examples; + +import org.opalj.fpcf.properties.immutability.field_assignability.AssignableField; + +/** + * The default value of the field x is assigned to another field n during construction and as + * a result seen with two different values. + */ +public class PrematurelyReadFinalField { + + @AssignableField("Field n is assigned with different values.") + static int n = 5; + + public static void main(String[] args) { + C c = new C(); + } +} + +class B { + B() { + PrematurelyReadFinalField.n = ((C) this).x; + } +} + +class C extends B { + + @AssignableField("Is seen with two different values during construction.") + public final int x; + + C() { + super(); + x = 3; + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/advanced_counter_examples/ThisEscapesDuringConstruction.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/advanced_counter_examples/ThisEscapesDuringConstruction.java new file mode 100644 index 0000000000..b6d2759f50 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/advanced_counter_examples/ThisEscapesDuringConstruction.java @@ -0,0 +1,23 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.immutability.openworld.assignability.advanced_counter_examples; + +import org.opalj.fpcf.properties.immutability.field_assignability.AssignableField; + +/** + * This test case simulates the fact that the `this` object escapes in the constructor before (final) fields + * are assigned. + */ +public class ThisEscapesDuringConstruction { + + @AssignableField("The this object escapes in the constructor before the field is assigned.") + final int n; + + public ThisEscapesDuringConstruction() { + C2.m(this); + n = 7; + } +} + +class C2 { + public static void m(ThisEscapesDuringConstruction c) {} +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/advanced_counter_examples/ValueReadBeforeAssignment.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/advanced_counter_examples/ValueReadBeforeAssignment.java new file mode 100644 index 0000000000..57d7c485f9 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/advanced_counter_examples/ValueReadBeforeAssignment.java @@ -0,0 +1,25 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.immutability.openworld.assignability.advanced_counter_examples; + +import org.opalj.fpcf.properties.immutability.field_assignability.AssignableField; + +/** + * The value of the field x is read with its default value (0) + * in the constructor before assignment and assigned to a public field. + * Thus, the value can be accessed from everywhere. + */ +public class ValueReadBeforeAssignment { + @AssignableField("Field value is read before assignment.") + private int x; + @AssignableField("Field y is public and not final.") + public int y; + + public ValueReadBeforeAssignment() { + y = x; + x = 42; + } + + public ValueReadBeforeAssignment foo() { + return new ValueReadBeforeAssignment(); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/clone_function/SimpleClonePattern.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/clone_function/SimpleClonePattern.java index cf68de1262..b14f671315 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/clone_function/SimpleClonePattern.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/assignability/clone_function/SimpleClonePattern.java @@ -15,7 +15,7 @@ public final class SimpleClonePattern { @TransitivelyImmutableField("Field is effectively non assignable and has a primitive type") - @EffectivelyNonAssignableField("Field is only assigned ones due to the clone function pattern") + @EffectivelyNonAssignableField("Field is only assigned once due to the clone function pattern") private int i; public SimpleClonePattern clone(){ @@ -28,7 +28,7 @@ public SimpleClonePattern clone(){ class CloneNonAssignableWithNewObject { @TransitivelyImmutableField("field is effectively non assignable and assigned with a transitively immutable object") - @EffectivelyNonAssignableField("field is only assigned ones due to the clone function pattern") + @EffectivelyNonAssignableField("field is only assigned once due to the clone function pattern") private Integer integer; public CloneNonAssignableWithNewObject clone(){ @@ -41,7 +41,7 @@ public CloneNonAssignableWithNewObject clone(){ class EscapesAfterAssignment { @TransitivelyImmutableField("field is effectively non assignable and assigned with a transitively immutable object") - @EffectivelyNonAssignableField("field is only assigned ones due to the clone function pattern") + @EffectivelyNonAssignableField("field is only assigned once due to the clone function pattern") private Integer integer; private Integer integerCopy; @@ -77,7 +77,7 @@ public MultipleFieldsAssignedInCloneFunction clone(){ class ConstructorWithParameter { @TransitivelyImmutableField("field is effectively non assignable and has a transitively immutable type") - @EffectivelyNonAssignableField("field is only assigned ones due to the clone function pattern") + @EffectivelyNonAssignableField("field is only assigned once due to the clone function pattern") private Integer integer; public ConstructorWithParameter(Integer integer){ diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/lazyinitialization/objects/DifferentLazyInitializedFieldTypes.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/lazyinitialization/objects/DifferentLazyInitializedFieldTypes.java index c3aa38cbfb..fa2f24d728 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/lazyinitialization/objects/DifferentLazyInitializedFieldTypes.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/immutability/openworld/lazyinitialization/objects/DifferentLazyInitializedFieldTypes.java @@ -16,8 +16,8 @@ */ public class DifferentLazyInitializedFieldTypes { - @TransitivelyImmutableField("Lazy initialized field with primitive type.") - @LazilyInitializedField("field is thread safely lazy initialized") + @TransitivelyImmutableField("Lazy initialized field with primitive type.") + @LazilyInitializedField("field is thread safely lazy initialized") private int inTheGetterLazyInitializedIntField; public synchronized int getInTheGetterLazyInitializedIntField(){ diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/immutability/FieldAssignability.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/immutability/FieldAssignability.scala index 53f61713a3..f942abfec4 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/immutability/FieldAssignability.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/immutability/FieldAssignability.scala @@ -34,6 +34,8 @@ sealed trait FieldAssignability extends OrderedProperty with FieldAssignabilityP final def key: PropertyKey[FieldAssignability] = FieldAssignability.key def isImmutable = false + + def meet(other: FieldAssignability): FieldAssignability } object FieldAssignability extends FieldAssignabilityPropertyMetaInformation { @@ -73,9 +75,9 @@ case object EffectivelyNonAssignable extends NonAssignableField { def meet(other: FieldAssignability): FieldAssignability = if (other == NonAssignable) { - other - } else { this + } else { + other } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/AbstractFieldAssignabilityAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/AbstractFieldAssignabilityAnalysis.scala index 4c1c4a86bd..a86a79d3dd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/AbstractFieldAssignabilityAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/AbstractFieldAssignabilityAnalysis.scala @@ -37,7 +37,6 @@ import org.opalj.br.fpcf.properties.EscapeInCallee import org.opalj.br.fpcf.properties.EscapeProperty import org.opalj.br.fpcf.properties.EscapeViaReturn import org.opalj.br.fpcf.properties.NoEscape -import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.fieldaccess.AccessReceiver import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation import org.opalj.br.fpcf.properties.immutability.Assignable @@ -59,8 +58,6 @@ import org.opalj.fpcf.SomeInterimEP import org.opalj.fpcf.UBP import org.opalj.tac.DUVar import org.opalj.tac.Stmt -import org.opalj.tac.TACMethodParameter -import org.opalj.tac.TACode import org.opalj.tac.common.DefinitionSite import org.opalj.tac.common.DefinitionSitesKey import org.opalj.tac.fpcf.properties.TACAI @@ -72,22 +69,22 @@ trait AbstractFieldAssignabilityAnalysis extends FPCFAnalysis { val field: Field var fieldAssignability: FieldAssignability = NonAssignable - var fieldAccesses: Map[DefinedMethod, Set[(PC, AccessReceiver)]] = Map.empty + var fieldAccesses: Map[Context, Set[(PC, AccessReceiver)]] = Map.empty var escapeDependees: Set[EOptionP[(Context, DefinitionSite), EscapeProperty]] = Set.empty var fieldWriteAccessDependee: Option[EOptionP[DeclaredField, FieldWriteAccessInformation]] = None var tacDependees: Map[DefinedMethod, EOptionP[Method, TACAI]] = Map.empty - var callerDependees: Map[DefinedMethod, EOptionP[DefinedMethod, Callers]] = Map.empty.withDefault { dm => - propertyStore(dm, Callers.key) - } + + def forEachFieldAccess(definedMethod: DefinedMethod)(f: (Context, Set[(PC, AccessReceiver)]) => Unit): Unit = + fieldAccesses.iterator.filter(_._1.method eq definedMethod).foreach(kv => f(kv._1, kv._2)) def hasDependees: Boolean = { escapeDependees.nonEmpty || fieldWriteAccessDependee.exists(_.isRefinable) || - tacDependees.valuesIterator.exists(_.isRefinable) || callerDependees.valuesIterator.exists(_.isRefinable) + tacDependees.valuesIterator.exists(_.isRefinable) } def dependees: Set[SomeEOptionP] = { escapeDependees ++ fieldWriteAccessDependee.filter(_.isRefinable) ++ - callerDependees.valuesIterator.filter(_.isRefinable) ++ tacDependees.valuesIterator.filter(_.isRefinable) + tacDependees.valuesIterator.filter(_.isRefinable) } } @@ -114,70 +111,139 @@ trait AbstractFieldAssignabilityAnalysis extends FPCFAnalysis { def createState(field: Field): AnalysisState - /** - * Analyzes the field's assignability. - * - * This analysis is only ''soundy'' if the class file does not contain native methods and reflections. - * Fields can be manipulated by any given native method. - * Because the analysis cannot be aware of any given native method, - * they are not considered as well as reflections. - */ - private[analyses] def determineFieldAssignability( - field: Field - ): ProperPropertyComputationResult = { - + private[analyses] def determineFieldAssignability(field: Field): ProperPropertyComputationResult = { implicit val state: AnalysisState = createState(field) - if (field.isFinal) - return Result(field, NonAssignable); - else - state.fieldAssignability = EffectivelyNonAssignable - - if (field.isPublic) - return Result(field, Assignable); - val thisType = field.classFile.thisType - - if (field.isPublic) { - if (typeExtensibility(ClassType.Object).isYesOrUnknown) { - return Result(field, Assignable); - } - } else if (field.isProtected) { - if (typeExtensibility(thisType).isYesOrUnknown) { + if (!field.isFinal) { + if (field.isPublic) { return Result(field, Assignable); + } else if (field.isProtected) { + if (typeExtensibility(thisType).isYesOrUnknown || !closedPackages(thisType.packageName)) { + return Result(field, Assignable); + } } - if (!closedPackages(thisType.packageName)) { + + if (field.isPackagePrivate && !closedPackages(thisType.packageName)) { return Result(field, Assignable); } } - if (field.isPackagePrivate) { - if (!closedPackages(thisType.packageName)) { - return Result(field, Assignable); + + state.fieldAssignability = + if (field.isFinal) NonAssignable + else EffectivelyNonAssignable + + handleWriteAccessInformation(propertyStore(declaredFields(field), FieldWriteAccessInformation.key)) + } + + protected def handleWriteAccessInformation( + newEP: EOptionP[DeclaredField, FieldWriteAccessInformation] + )(implicit state: AnalysisState): ProperPropertyComputationResult = { + if (newEP.hasUBP) { + val newFai = newEP.ub + val (seenDirectAccesses, seenIndirectAccesses) = state.fieldWriteAccessDependee match { + case Some(UBP(fai)) => (fai.numDirectAccesses, fai.numIndirectAccesses) + case _ => (0, 0) } - } + state.fieldWriteAccessDependee = Some(newEP) - val fwaiEP = propertyStore(declaredFields(field), FieldWriteAccessInformation.key) + // Register all field accesses in the state first to enable cross access comparisons + newFai.getNewestAccesses( + newFai.numDirectAccesses - seenDirectAccesses, + newFai.numIndirectAccesses - seenIndirectAccesses + ) foreach { case (contextID, pc, receiver, _) => + val context = contextProvider.contextFromId(contextID) + val access = (pc, receiver) + state.fieldAccesses = state.fieldAccesses.updatedWith(context) { + case None => Some(Set(access)) + case Some(accesses) => Some(accesses + access) + } + } + + // Then determine assignability impact per access individually + newFai.getNewestAccesses( + newFai.numDirectAccesses - seenDirectAccesses, + newFai.numIndirectAccesses - seenIndirectAccesses + ) foreach { case (contextID, pc, receiver, _) => + if (state.fieldAssignability != Assignable) { + val context = contextProvider.contextFromId(contextID) + val method = context.method.asDefinedMethod + val tacEP = state.tacDependees.get(method) match { + case Some(tacEP) => tacEP + case None => + val tacEP = propertyStore(method.definedMethod, TACAI.key) + state.tacDependees += method -> tacEP + tacEP + } - if (handleFieldWriteAccessInformation(fwaiEP)) - return Result(field, Assignable); + if (tacEP.hasUBP) { + state.fieldAssignability = state.fieldAssignability.meet { + determineAssignabilityFromWriteInContext(context, method, tacEP.ub.tac.get, pc, receiver) + } + } + } + } + } else { + state.fieldWriteAccessDependee = Some(newEP) + } createResult() } /** - * Analyzes field writes for a single method, returning false if the field may still be - * effectively final and true otherwise. + * @return The assignability inferrable from the relationship of the given write to other writes / reads in the + * same context. + * + * @note Callers must register ALL writes discovered so far in the state before calling this method. + * TODO think about if this is really necessary */ - def methodUpdatesField( - method: DefinedMethod, - taCode: TACode[TACMethodParameter, V], - callers: Callers, - pc: PC, - receiver: AccessReceiver - )(implicit state: AnalysisState): Boolean + protected def determineAssignabilityFromWriteInContext( + context: Context, + definedMethod: DefinedMethod, + taCode: TACode[TACMethodParameter, V], + writePC: PC, + receiver: AccessReceiver + )(implicit state: AnalysisState): FieldAssignability + + def createResult()(implicit state: AnalysisState): ProperPropertyComputationResult = { + if (state.hasDependees && (state.fieldAssignability ne Assignable)) + InterimResult(state.field, lb = Assignable, ub = state.fieldAssignability, state.dependees, continuation) + else + Result(state.field, state.fieldAssignability) + } + + def continuation(eps: SomeEPS)(implicit state: AnalysisState): ProperPropertyComputationResult = { + eps.pk match { + case FieldWriteAccessInformation.key => + handleWriteAccessInformation(eps.asInstanceOf[EOptionP[DeclaredField, FieldWriteAccessInformation]]) + + case EscapeProperty.key => + val newEP = eps.asInstanceOf[EOptionP[(Context, DefinitionSite), EscapeProperty]] + state.escapeDependees = state.escapeDependees.filter(_.e != newEP.e) + if (handleEscapeProperty(newEP)) + state.fieldAssignability = Assignable + createResult() + + case TACAI.key => + val newEP = eps.asInstanceOf[EOptionP[Method, TACAI]] + val method = declaredMethods(newEP.e) + state.tacDependees += method -> newEP + // Renew field assignability analysis for all field accesses + state.forEachFieldAccess(method) { (context, accesses) => + val taCode = newEP.ub.tac.get + accesses.foreach { case (pc, receiver) => + if (state.fieldAssignability != Assignable) + state.fieldAssignability = state.fieldAssignability.meet { + determineAssignabilityFromWriteInContext(context, method, taCode, pc, receiver) + } + } + } + createResult() + } + } /** - * Handles the influence of an escape property on the field immutability. + * Handles the influence of an escape property on the field assignability. * * @return true if the object - on which a field write occurred - escapes, false otherwise. * @note (Re-)Adds dependees as necessary. @@ -210,136 +276,27 @@ trait AbstractFieldAssignabilityAnalysis extends FPCFAnalysis { } /** - * Checks whether the object reference of a PutField does not escape (except for being returned). + * Checks whether the object reference of a field access does not escape (except for being returned). */ - def referenceHasNotEscaped( + def referenceHasEscaped( ref: V, stmts: Array[Stmt[V]], method: DefinedMethod, - callers: Callers + context: Context )(implicit state: AnalysisState): Boolean = { ref.definedBy.forall { defSite => - if (defSite < 0) false // Must be locally created + if (defSite < 0) true // Must be locally created else { val definition = stmts(defSite).asAssignment // Must either be null or freshly allocated - if (definition.expr.isNullExpr) true - else if (!definition.expr.isNew) false + if (definition.expr.isNullExpr) false + else if (!definition.expr.isNew) true else { - var hasEscaped = false - callers.forNewCalleeContexts(null, method) { context => - val entity = (context, definitionSites(method, definition.pc)) - val escapeProperty = propertyStore(entity, EscapeProperty.key) - hasEscaped ||= handleEscapeProperty(escapeProperty) - } - !hasEscaped - } - } - } - } - - protected[this] def handleFieldWriteAccessInformation( - newEP: EOptionP[DeclaredField, FieldWriteAccessInformation] - )(implicit state: AnalysisState): Boolean = { - val assignable = if (newEP.hasUBP) { - val newFai = newEP.ub - val (seenDirectAccesses, seenIndirectAccesses) = state.fieldWriteAccessDependee match { - case Some(UBP(fai)) => (fai.numDirectAccesses, fai.numIndirectAccesses) - case _ => (0, 0) - } - state.fieldWriteAccessDependee = Some(newEP) - - newFai.getNewestAccesses( - newFai.numDirectAccesses - seenDirectAccesses, - newFai.numIndirectAccesses - seenIndirectAccesses - ) exists { case (contextID, pc, receiver, _) => - val method = contextProvider.contextFromId(contextID).method.asDefinedMethod - state.fieldAccesses += method -> (state.fieldAccesses.getOrElse(method, Set.empty) + - ((pc, receiver))) - - val tacEP = state.tacDependees.get(method) match { - case Some(tacEP) => tacEP - case None => - val tacEP = propertyStore(method.definedMethod, TACAI.key) - state.tacDependees += method -> tacEP - tacEP - } - - val callersEP = state.callerDependees.get(method) match { - case Some(callersEP) => callersEP - case None => - val callersEP = propertyStore(method, Callers.key) - state.callerDependees += method -> callersEP - callersEP + val entity = (context, definitionSites(method, definition.pc)) + val escapeProperty = propertyStore(entity, EscapeProperty.key) + handleEscapeProperty(escapeProperty) } - - if (tacEP.hasUBP && callersEP.hasUBP) - methodUpdatesField(method, tacEP.ub.tac.get, callersEP.ub, pc, receiver) - else - false } - } else { - state.fieldWriteAccessDependee = Some(newEP) - false - } - - assignable - } - - /** - * Continuation function handling updates to the FieldPrematurelyRead property or to the purity - * property of the method that initializes a (potentially) lazy initialized field. - */ - def c(eps: SomeEPS)(implicit state: AnalysisState): ProperPropertyComputationResult = { - val isNonFinal = eps.pk match { - case EscapeProperty.key => - val newEP = eps.asInstanceOf[EOptionP[(Context, DefinitionSite), EscapeProperty]] - state.escapeDependees = state.escapeDependees.filter(_.e != newEP.e) - handleEscapeProperty(newEP) - case TACAI.key => - val newEP = eps.asInstanceOf[EOptionP[Method, TACAI]] - val method = declaredMethods(newEP.e) - val accesses = state.fieldAccesses.get(method) - state.tacDependees += method -> newEP - val callersProperty = state.callerDependees(method) - if (callersProperty.hasUBP && accesses.isDefined) - accesses.get.exists(access => - methodUpdatesField(method, newEP.ub.tac.get, callersProperty.ub, access._1, access._2) - ) - else false - case Callers.key => - val newEP = eps.asInstanceOf[EOptionP[DefinedMethod, Callers]] - val method = newEP.e - val accesses = state.fieldAccesses.get(method) - state.callerDependees += newEP.e -> newEP - val tacProperty = state.tacDependees(method) - if (tacProperty.hasUBP && tacProperty.ub.tac.isDefined && accesses.isDefined) - accesses.get.exists(access => - methodUpdatesField(method, tacProperty.ub.tac.get, newEP.ub, access._1, access._2) - ) - else false - case FieldWriteAccessInformation.key => - val newEP = eps.asInstanceOf[EOptionP[DeclaredField, FieldWriteAccessInformation]] - handleFieldWriteAccessInformation(newEP) - } - - if (isNonFinal) - Result(state.field, Assignable) - else - createResult() - } - - def createResult()(implicit state: AnalysisState): ProperPropertyComputationResult = { - if (state.hasDependees && (state.fieldAssignability ne Assignable)) { - InterimResult( - state.field, - Assignable, - state.fieldAssignability, - state.dependees, - c - ) - } else { - Result(state.field, state.fieldAssignability) } } @@ -368,8 +325,7 @@ trait AbstractFieldAssignabilityAnalysisScheduler extends FPCFAnalysisScheduler override def uses: Set[PropertyBounds] = PropertyBounds.ubs( TACAI, EscapeProperty, - FieldWriteAccessInformation, - Callers + FieldWriteAccessInformation ) override def requiredProjectInformation: ProjectInformationKeys = Seq( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/L1FieldAssignabilityAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/L1FieldAssignabilityAnalysis.scala index 1d0ed2eb20..b6127ab62e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/L1FieldAssignabilityAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/L1FieldAssignabilityAnalysis.scala @@ -12,9 +12,11 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.BasicFPCFEagerAnalysisScheduler import org.opalj.br.fpcf.BasicFPCFLazyAnalysisScheduler import org.opalj.br.fpcf.FPCFAnalysis -import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.fieldaccess.AccessReceiver +import org.opalj.br.fpcf.properties.immutability.Assignable import org.opalj.br.fpcf.properties.immutability.FieldAssignability +import org.opalj.br.fpcf.properties.immutability.NonAssignable import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.cg.uVarForDefSites @@ -36,37 +38,45 @@ class L1FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) type AnalysisState = State override def createState(field: Field): AnalysisState = State(field) - /** - * Analyzes field writes for a single method, returning false if the field may still be - * effectively final and true otherwise. - */ - def methodUpdatesField( + override def determineAssignabilityFromWriteInContext( + context: Context, definedMethod: DefinedMethod, taCode: TACode[TACMethodParameter, V], - callers: Callers, - pc: PC, + writePC: PC, receiver: AccessReceiver - )(implicit state: AnalysisState): Boolean = { - val stmts = taCode.stmts + )(implicit state: AnalysisState): FieldAssignability = { + val field = state.field val method = definedMethod.definedMethod - if (receiver.isDefined) { - val objRef = uVarForDefSites(receiver.get, taCode.pcToIndex).asVar - // note that here we assume real three address code (flat hierarchy) - - // for instance fields it is okay if they are written in the - // constructor (w.r.t. the currently initialized object!) + if (field.isStatic && method.isConstructor) { + // A static field updated in an arbitrary constructor may be updated with (at least) the first call. + // Thus, we may see its initial value or the updated value, making the field assignable. + return Assignable; + } - // If the field that is written is not the one referred to by the - // self reference, it is not effectively final. + if (state.fieldAccesses(context).size > 1) { + // Multi-branch access detection is not available on this level. + return Assignable; + } - // However, a method (e.g. clone) may instantiate a new object and - // write the field as long as that new object did not yet escape. - (!method.isConstructor || - objRef.definedBy != SelfReferenceParameter) && - !referenceHasNotEscaped(objRef, stmts, definedMethod, callers) + val receiverVarOpt = receiver.map(uVarForDefSites(_, taCode.pcToIndex)) + if (receiverVarOpt.isDefined) { + val receiverVar = receiverVarOpt.get + if (method.isConstructor && receiverVar.definedBy == SelfReferenceParameter) { + // for instance fields it is okay if they are written in the + // constructor (w.r.t. the currently initialized object!) + NonAssignable + } else if (!referenceHasEscaped(receiverVar, taCode.stmts, definedMethod, context)) { + // A method (e.g. clone) may instantiate a new object and write the field as long as that new object + // did not yet escape. + NonAssignable + } else { + Assignable + } + } else if (!method.isStaticInitializer) { + Assignable } else { - !method.isStaticInitializer + NonAssignable } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/L2FieldAssignabilityAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/L2FieldAssignabilityAnalysis.scala index f2525500e5..1c2a9b719a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/L2FieldAssignabilityAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldassignability/L2FieldAssignabilityAnalysis.scala @@ -11,7 +11,6 @@ import scala.collection.mutable import org.opalj.RelationalOperators.EQ import org.opalj.RelationalOperators.NE -import org.opalj.ai.isFormalParameter import org.opalj.br.ClassType import org.opalj.br.DeclaredField import org.opalj.br.DefinedMethod @@ -26,7 +25,7 @@ import org.opalj.br.cfg.CFGNode import org.opalj.br.fpcf.BasicFPCFEagerAnalysisScheduler import org.opalj.br.fpcf.BasicFPCFLazyAnalysisScheduler import org.opalj.br.fpcf.FPCFAnalysis -import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.fieldaccess.AccessParameter import org.opalj.br.fpcf.properties.fieldaccess.AccessReceiver import org.opalj.br.fpcf.properties.fieldaccess.FieldReadAccessInformation @@ -34,6 +33,7 @@ import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation import org.opalj.br.fpcf.properties.immutability.Assignable import org.opalj.br.fpcf.properties.immutability.FieldAssignability import org.opalj.br.fpcf.properties.immutability.LazilyInitialized +import org.opalj.br.fpcf.properties.immutability.NonAssignable import org.opalj.br.fpcf.properties.immutability.UnsafelyLazilyInitialized import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.EOptionP @@ -66,129 +66,197 @@ import org.opalj.tac.fpcf.analyses.cg.uVarForDefSites /** * Determines the assignability of a field. * + * This analysis relies on the field access information provided by the framework, and can thus only detect field + * accesses provided by the framework. If one needs to include reflective or native accesses, the respective modules + * for the field access information need to be included in the analysis. + * * @note Requires that the 3-address code's expressions are not deeply nested. * @author Tobias Roth * @author Dominik Helm * @author Florian Kübler * @author Michael Eichberg + * @author Maximilian Rüsch */ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) extends AbstractFieldAssignabilityAnalysis with FPCFAnalysis { - val considerLazyInitialization: Boolean = + private val considerLazyInitialization: Boolean = project.config.getBoolean( "org.opalj.fpcf.analyses.L2FieldAssignabilityAnalysis.considerLazyInitialization" ) - /** - * Analyzes field writes for a single method, returning false if the field may still be - * effectively final and true otherwise. - */ - def methodUpdatesField( + case class State( + field: Field + ) extends AbstractFieldAssignabilityAnalysisState { + var checkLazyInit: Option[(Method, Int, Int, TACode[TACMethodParameter, V])] = None + var writesToCheckForReadDominance = List.empty[(DefinedMethod, PC, Option[V])] + + var fieldReadAccessDependee: Option[EOptionP[DeclaredField, FieldReadAccessInformation]] = None + + override def hasDependees: Boolean = fieldReadAccessDependee.exists(_.isRefinable) || super.hasDependees + + override def dependees: Set[SomeEOptionP] = super.dependees ++ fieldReadAccessDependee.filter(_.isRefinable) + } + + type AnalysisState = State + override def createState(field: Field): AnalysisState = State(field) + + override protected def handleWriteAccessInformation( + newEP: EOptionP[DeclaredField, FieldWriteAccessInformation] + )(implicit state: State): ProperPropertyComputationResult = { + if (state.checkLazyInit.isDefined && hasMultipleNonConstructorWrites(state.checkLazyInit.get._1)) { + state.fieldAssignability = Assignable + createResult() + } else { + super.handleWriteAccessInformation(newEP) + } + } + + override def determineAssignabilityFromWriteInContext( + context: Context, definedMethod: DefinedMethod, taCode: TACode[TACMethodParameter, V], - callers: Callers, - pc: PC, + writePC: PC, receiver: AccessReceiver - )(implicit state: AnalysisState): Boolean = { + )(implicit state: AnalysisState): FieldAssignability = { val field = state.field val method = definedMethod.definedMethod - val stmts = taCode.stmts - val receiverVar = receiver.map(uVarForDefSites(_, taCode.pcToIndex)) + val writeIndex = taCode.pcToIndex(writePC) + if (field.isStatic && method.isConstructor) { + // A static field updated in an arbitrary constructor may be updated with (at least) the first call. + // Thus, we may see its initial value or the updated value, making the field assignable. + return Assignable; + } - val index = taCode.pcToIndex(pc) - if (method.isInitializer) { - if (field.isStatic) { - method.isConstructor - } else { - receiverVar.isDefined && receiverVar.get.definedBy != SelfReferenceParameter + if (state.fieldAccesses(context).exists { case (otherWritePC, _) => + writePC != otherWritePC && ( + dominates(writeIndex, taCode.pcToIndex(otherWritePC), taCode) || + dominates(taCode.pcToIndex(otherWritePC), writeIndex, taCode) + ) + } + ) { + // When one write is detected to dominate another within the same method, the field is definitively assigned + // multiple times and cannot be effectively non-assignable, even in initializers. + // IMPROVE reduce this to modifications on the same instance and consider cases where not every path + // contains multiple writes to be more sound. + return Assignable; + } + + val receiverVarOpt = receiver.map(uVarForDefSites(_, taCode.pcToIndex)) + // If we have no information about the receiver, but we should have it, soundly return true. + if (field.isNotStatic && receiverVarOpt.isEmpty) + return Assignable; + + if (method.isInitializer && method.classFile == field.classFile) { + if (field.isNotStatic && receiverVarOpt.get.definedBy != SelfReferenceParameter) { + // An instance field that is modified in an initializer of a different class must be assignable + return Assignable; } } else { - if (field.isStatic || receiverVar.isDefined && receiverVar.get.definedBy == SelfReferenceParameter) { + if (field.isStatic || + receiverVarOpt.isDefined && receiverVarOpt.get.definedBy == SelfReferenceParameter + ) { // A field written outside an initializer must be lazily initialized or it is assignable if (considerLazyInitialization) { - isAssignable(index, getDefaultValues(), method, taCode) + return determineLazyInitialization(writeIndex, getDefaultValues(), method, taCode); } else - true - } else if (receiverVar.isDefined && !referenceHasNotEscaped(receiverVar.get, stmts, definedMethod, callers)) { - // Here the clone pattern is determined among others - // - // note that here we assume real three address code (flat hierarchy) - - // for instance fields it is okay if they are written in the - // constructor (w.r.t. the currently initialized object!) - - // If the field that is written is not the one referred to by the - // self reference, it is not effectively final. - - // However, a method (e.g. clone) may instantiate a new object and - // write the field as long as that new object did not yet escape. - true - } else { - checkWriteDominance(definedMethod, taCode, receiverVar, index) + return Assignable; + } else if (receiverVarOpt.isDefined && + referenceHasEscaped(receiverVarOpt.get, taCode.stmts, definedMethod, context) + ) { + // Arbitrary methods may instantiate new objects and write instance fields, as long as the new object did + // not yet escape. This effectively determines usage of the `clone` pattern. If the reference has escaped, + // we need to assume that someone observed the old field value before modification, thus soundly return. + return Assignable; } + } + if ((field.isNotStatic && + isInstanceUsedSuspiciously(context, definedMethod, taCode, writeIndex, receiverVarOpt.get)) || + !doesWriteDominateAllReads(definedMethod, receiverVarOpt, writeIndex) + ) { + Assignable + } else { + NonAssignable } } - private def checkWriteDominance( + /** + * @return Whether the given instance on which a field is written is used in a statement that cannot be identified + * as preserving non-assignability, in which case we can soundly return. + * + * IMPROVE handle static fields and obfuscated variable uses + */ + private def isInstanceUsedSuspiciously( + context: Context, definedMethod: DefinedMethod, taCode: TACode[TACMethodParameter, V], - receiverVar: Option[V], - index: Int + writeIndex: Int, + receiverVar: V )(implicit state: State): Boolean = { val stmts = taCode.stmts - - val writes = state.fieldWriteAccessDependee.get.ub.accesses - val writesInMethod = writes.filter { w => contextProvider.contextFromId(w._1).method eq definedMethod }.toSeq - - if (writesInMethod.distinctBy(_._2).size > 1) - return true; // Field is written in multiple locations, thus must be assignable - - // If we have no information about the receiver, we soundly return - if (receiverVar.isEmpty) + if (receiverVar.definedBy.size != 1) return true; - val assignedValueObject = receiverVar.get - if (assignedValueObject.definedBy.exists(_ < 0)) + val defSite = receiverVar.definedBy.head + if (defSite < -1 || (defSite == -1 && !definedMethod.definedMethod.isConstructor)) return true; - val assignedValueObjectVar = stmts(assignedValueObject.definedBy.head).asAssignment.targetVar.asVar - - val fieldWriteInMethodIndex = taCode.pcToIndex(writesInMethod.head._2) - if (assignedValueObjectVar != null && !assignedValueObjectVar.usedBy.forall { index => - val stmt = stmts(index) - - fieldWriteInMethodIndex == index || // The value is itself written to another object - // IMPROVE: Can we use field access information to care about reflective accesses here? - stmt.isPutField && stmt.asPutField.name != state.field.name || - stmt.isAssignment && stmt.asAssignment.targetVar == assignedValueObjectVar || - stmt.isMethodCall && stmt.asMethodCall.name == "" || - // CHECK do we really need the taCode here? - dominates(fieldWriteInMethodIndex, index, taCode) - } - ) - return true; - - val writeAccess = (definedMethod, taCode, receiverVar, index) + val uses = if (defSite == -1) + taCode.params.thisParameter.useSites + else + stmts(defSite).asAssignment.targetVar.asVar.usedBy + + // Analyze uses of the current variable for suspicious usage while excluding known safe cases + val writeIndices = state.fieldAccesses(context).map(access => taCode.pcToIndex(access._1)) + uses.exists { index => + val stmt = stmts(index) + + // We ignore any writes (possibly disruptive reads are checked for dominance later) + !writeIndices.contains(index) && + // ... and ignore fresh initializations of the object, as they cannot read fields ... + !(stmt.isMethodCall + && stmt.asMethodCall.name == "" + && (stmt.asMethodCall.declaringClass eq ClassType.Object)) && + // ... and ignore easily recognizable assignments of other fields (again, reads are checked later) ... + // IMPROVE: Use field access information to incorporate reflective accesses + !(stmt.isPutField && stmt.asPutField.name != state.field.name) && + // ... and ignore easily recognizable field reads of arbitrary fields on the current instance ... + stmt.forallSubExpressions(expr => + !expr.isGetField || + !expr.asGetField.objRef.asVar.definedBy.contains(defSite) + ) && + // ... and ignore the case in which the statement is dominated by the given write anyway + !dominates(writeIndex, index, taCode) + } + } + /** + * @return Whether the given write dominates all reads. Soundly returns true if this cannot be determined yet. + */ + private def doesWriteDominateAllReads( + definedMethod: DefinedMethod, + receiverVar: Option[V], + writeIndex: Int + )(implicit state: State): Boolean = { if (state.fieldReadAccessDependee.isEmpty) { state.fieldReadAccessDependee = Some(propertyStore(declaredFields(state.field), FieldReadAccessInformation.key)) } val fraiEP = state.fieldReadAccessDependee.get + val writeAccess = (definedMethod, writeIndex, receiverVar) + if (fraiEP.hasUBP && fieldReadsNotDominated(fraiEP.ub.accesses, Seq(writeAccess))) + return false; - if (fraiEP.hasUBP && fieldReadsNotDominated(fraiEP.ub, 0, 0, Seq(writeAccess))) - return true; - - state.openWrites ::= writeAccess + if (fraiEP.isRefinable) + state.writesToCheckForReadDominance ::= writeAccess - false + true } - override def c(eps: SomeEPS)(implicit state: State): ProperPropertyComputationResult = { + override def continuation(eps: SomeEPS)(implicit state: State): ProperPropertyComputationResult = { eps.pk match { case FieldReadAccessInformation.key => val newEP = eps.asInstanceOf[EOptionP[DeclaredField, FieldReadAccessInformation]] @@ -197,23 +265,14 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) case Some(UBP(fai)) => (fai.numDirectAccesses, fai.numIndirectAccesses) case _ => (0, 0) } + val newestReads = reads.getNewestAccesses(seenDirectAccesses, seenIndirectAccesses) - if (fieldReadsNotDominated(reads, seenDirectAccesses, seenIndirectAccesses, state.openWrites)) + if (fieldReadsNotDominated(newestReads, state.writesToCheckForReadDominance)) return Result(state.field, Assignable); if (state.checkLazyInit.isDefined) { val (method, guardIndex, writeIndex, taCode) = state.checkLazyInit.get - if (doFieldReadsEscape( - reads.getNewestAccesses( - reads.numDirectAccesses - seenDirectAccesses, - reads.numIndirectAccesses - seenIndirectAccesses - ).toSeq, - method, - guardIndex, - writeIndex, - taCode - ) - ) + if (doFieldReadsEscape(newestReads.toSeq, method, guardIndex, writeIndex, taCode)) return Result(state.field, Assignable); } @@ -221,70 +280,46 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) createResult() case _ => - super.c(eps) + super.continuation(eps) } } - override protected[this] def handleFieldWriteAccessInformation( - newEP: EOptionP[DeclaredField, FieldWriteAccessInformation] - )(implicit state: State): Boolean = { - val openWrites = state.openWrites - state.openWrites = List.empty - - state.checkLazyInit.isDefined && hasMultipleNonConstructorWrites(state.checkLazyInit.get._1) || - super.handleFieldWriteAccessInformation(newEP) || - openWrites.exists { case (method, tac, receiver, index) => - checkWriteDominance(method, tac, receiver, index) - } - } - + /** + * @return Whether there is at least one given read such that there is at least one given write in the same method + * that does not dominate the read. + */ private def fieldReadsNotDominated( - fieldReadAccessInformation: FieldReadAccessInformation, - seenDirectAccesses: Int, - seenIndirectAccesses: Int, - writes: Seq[(DefinedMethod, TACode[TACMethodParameter, V], Option[V], Int)] + reads: IterableOnce[(Int, PC, AccessReceiver, AccessParameter)], + writes: Seq[(DefinedMethod, Int, Option[V])] )(implicit state: State): Boolean = { - writes.exists { case (writeMethod, _, _, writeIndex) => - fieldReadAccessInformation.getNewestAccesses( - fieldReadAccessInformation.numDirectAccesses - seenDirectAccesses, - fieldReadAccessInformation.numIndirectAccesses - seenIndirectAccesses - ).exists { case (readContextID, readPC, readReceiver, _) => - val method = contextProvider.contextFromId(readContextID).method - (writeMethod eq method) && { - val taCode = state.tacDependees(method.asDefinedMethod).ub.tac.get - - if (readReceiver.isDefined && readReceiver.get._2.forall(isFormalParameter)) { - false - } else { - !dominates(writeIndex, taCode.pcToIndex(readPC), taCode) + reads.iterator.exists { + case (readContextID, readPC, readReceiver, _) => + val readMethod = contextProvider.contextFromId(readContextID).method + writes.exists { case (writeMethod, writeIndex, writeReceiverVarOpt) => + (readMethod eq writeMethod) && { + val taCode = state.tacDependees(readMethod.asDefinedMethod).ub.tac.get + val readReceiverVarOpt = readReceiver.map(uVarForDefSites(_, taCode.pcToIndex)) + // A field read needs to be dominated if the field is static ... + if (state.field.isStatic || + // ... OR we can infer no information about the read receiver ... + readReceiverVarOpt.isEmpty || + // ... OR the read receiver is the write receiver, i.e. they have overlapping def sites + // IMPROVE this does not consider obfuscating reassignments, consider using PDUWebs + readReceiverVarOpt.get.definedBy.intersect(writeReceiverVarOpt.get.definedBy).nonEmpty + ) { + !dominates(writeIndex, taCode.pcToIndex(readPC), taCode) + } else + false } } - } } } - case class State( - field: Field - ) extends AbstractFieldAssignabilityAnalysisState { - var checkLazyInit: Option[(Method, Int, Int, TACode[TACMethodParameter, V])] = None - var openWrites = List.empty[(DefinedMethod, TACode[TACMethodParameter, V], Option[V], PC)] - - var fieldReadAccessDependee: Option[EOptionP[DeclaredField, FieldReadAccessInformation]] = None - - override def hasDependees: Boolean = fieldReadAccessDependee.exists(_.isRefinable) || super.hasDependees - - override def dependees: Set[SomeEOptionP] = super.dependees ++ fieldReadAccessDependee.filter(_.isRefinable) - } - - type AnalysisState = State - - override def createState(field: Field): AnalysisState = State(field) - /** * Determines whether the basic block of a given index dominates the basic block of the other index or is executed * before the other index in the case of both indexes belonging to the same basic block. */ - def dominates( + private def dominates( potentiallyDominatorIndex: Int, potentiallyDominatedIndex: Int, taCode: TACode[TACMethodParameter, V] @@ -296,24 +331,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) bbPotentiallyDominator == bbPotentiallyDominated && potentiallyDominatorIndex < potentiallyDominatedIndex } - // lazy initialization: - - /** - * Handles the lazy initialization determination for a field write in a given method - * @author Tobias Roth - * @return true if no lazy initialization was recognized - */ - def isAssignable( - writeIndex: Int, - defaultValues: Set[Any], - method: Method, - taCode: TACode[TACMethodParameter, V] - )(implicit state: AnalysisState): Boolean = { - state.fieldAssignability = determineLazyInitialization(writeIndex, defaultValues, method, taCode) - state.fieldAssignability eq Assignable - } - - def hasMultipleNonConstructorWrites(method: Method)(implicit state: AnalysisState): Boolean = { + private def hasMultipleNonConstructorWrites(method: Method)(implicit state: AnalysisState): Boolean = { val writes = state.fieldWriteAccessDependee.get.ub.accesses.toSeq // prevents writes outside the method and the constructor @@ -321,14 +339,14 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) val accessingMethod = contextProvider.contextFromId(w._1).method.definedMethod (accessingMethod ne method) && !accessingMethod.isInitializer }) || - writes.iterator.distinctBy(_._1).size < writes.size // More than one write per method was detected + writes.iterator.distinctBy(_._1).size < writes.size // More than one field write per method was detected } /** * Determines the kind of lazy initialization of a given field in the given method through a given field write. * @author Tobias Roth */ - def determineLazyInitialization( + private def determineLazyInitialization( writeIndex: Int, defaultValues: Set[Any], method: Method, @@ -428,7 +446,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) Assignable } - def doFieldReadsEscape( + private def doFieldReadsEscape( reads: Seq[(Int, PC, AccessReceiver, AccessParameter)], method: Method, guardIndex: Int, @@ -501,7 +519,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) * The throw statements: (the pc, the definitionSites, the bb of the throw statement) * @author Tobias Roth */ - def findCatchesAndThrows( + private def findCatchesAndThrows( tacCode: TACode[TACMethodParameter, V] ): (List[(Int, IntTrieSet)], List[(Int, IntTrieSet)]) = { var caughtExceptions: List[(Int, IntTrieSet)] = List.empty @@ -531,7 +549,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) * @return the index of the monitor enter and exit * @author Tobias Roth */ - def findMonitors( + private def findMonitors( fieldWrite: Int, tacCode: TACode[TACMethodParameter, V] )(implicit state: State): (Option[Int], Option[Int]) = { @@ -624,7 +642,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) * first statement executed if the field does not have its default value and the index of the * field read used for the guard and the index of the field-read. */ - def findGuards( + private def findGuards( fieldWrite: Int, defaultValues: Set[Any], taCode: TACode[TACMethodParameter, V] @@ -722,7 +740,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) /** * Returns all predecessor BasicBlocks of a CFGNode. */ - def getPredecessors(node: CFGNode, visited: Set[CFGNode]): List[BasicBlock] = { + private def getPredecessors(node: CFGNode, visited: Set[CFGNode]): List[BasicBlock] = { def getPredecessorsInternal(node: CFGNode, visited: Set[CFGNode]): Iterator[BasicBlock] = { node.predecessors.iterator.flatMap { currentNode => if (currentNode.isBasicBlock) { @@ -740,7 +758,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) /** * Determines whether a node is a transitive predecessor of another node. */ - def isTransitivePredecessor(possiblePredecessor: CFGNode, node: CFGNode): Boolean = { + private def isTransitivePredecessor(possiblePredecessor: CFGNode, node: CFGNode): Boolean = { val visited: mutable.Set[CFGNode] = mutable.Set.empty @@ -760,7 +778,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) /** * Returns all successors BasicBlocks of a CFGNode */ - def getSuccessors(node: CFGNode, visited: Set[CFGNode]): List[BasicBlock] = { + private def getSuccessors(node: CFGNode, visited: Set[CFGNode]): List[BasicBlock] = { def getSuccessorsInternal(node: CFGNode, visited: Set[CFGNode]): Iterator[BasicBlock] = { node.successors.iterator flatMap { currentNode => if (currentNode.isBasicBlock) @@ -776,7 +794,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) * Checks if an expression is a field read of the currently analyzed field. * For instance fields, the read must be on the `this` reference. */ - def isReadOfCurrentField( + private def isReadOfCurrentField( expr: Expr[V], tacCode: TACode[TACMethodParameter, V], index: Int @@ -831,7 +849,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) * Determines if an if-Statement is actually a guard for the current field, i.e. it compares * the current field to the default value. */ - def isGuard( + private def isGuard( ifStmt: If[V], defaultValues: Set[Any], code: Array[Stmt[V]], @@ -930,7 +948,7 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) /** * Checks that the returned value is definitely read from the field. */ - def isFieldValueReturned( + private def isFieldValueReturned( write: FieldWriteAccessStmt[V], writeIndex: Int, readIndex: Int, @@ -989,7 +1007,6 @@ class L2FieldAssignabilityAnalysis private[analyses] (val project: SomeProject) } } } - } trait L2FieldAssignabilityAnalysisScheduler extends AbstractFieldAssignabilityAnalysisScheduler { @@ -1022,11 +1039,7 @@ object LazyL2FieldAssignabilityAnalysis extends L2FieldAssignabilityAnalysisSche override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) - override final def register( - p: SomeProject, - ps: PropertyStore, - unused: Null - ): FPCFAnalysis = { + override final def register(p: SomeProject, ps: PropertyStore, unused: Null): FPCFAnalysis = { val analysis = new L2FieldAssignabilityAnalysis(p) ps.registerLazyPropertyComputation( FieldAssignability.key,